commit fa8daa4e7cdf50ed9ad714164e733c8ea6cad03b
parent e2c558eee2f91e87d2667c84d36c55980cd1fe01
Author: MTRNord <mtrnord1@gmail.com>
Date: Mon, 7 Feb 2022 20:51:41 +0100
Mock out client in tests to not run into race conditions causing wrong state due to timeout.
Diffstat:
11 files changed, 150 insertions(+), 70 deletions(-)
diff --git a/.env.test b/.env.test
@@ -0,0 +1 @@
+NEXT_PUBLIC_ENV="test"
+\ No newline at end of file
diff --git a/components/FrontPageImage.tsx b/components/FrontPageImage.tsx
@@ -58,7 +58,7 @@ export default class FrontPageImage extends PureComponent<Props, State> {
try {
const profile = await this.context.client.getProfile(this.props.event.sender);
this.setState({
- displayname: profile.displayname,
+ displayname: profile.displayname || this.props.event.sender,
});
} catch (error) {
console.debug(`Failed to fetch profile for user ${this.props.event.sender}:`, error);
@@ -68,7 +68,6 @@ export default class FrontPageImage extends PureComponent<Props, State> {
}
async registerAsGuest() {
- console.log(this.context.is_generating_guest);
if (this.context.is_generating_guest) {
return;
}
diff --git a/components/Header.tsx b/components/Header.tsx
@@ -1,5 +1,5 @@
import Link from "next/link";
-import { Component } from "react";
+import { PureComponent } from "react";
import User from "../helpers/db/Users";
import { ClientContext } from "./ClientContext";
import { i18n } from 'next-i18next';
@@ -12,30 +12,18 @@ type State = {
directory_data: User[];
loading: boolean;
error?: string;
- loggedIn?: boolean;
};
-export default class Header extends Component<Props, State> {
+export default class Header extends PureComponent<Props, State> {
declare context: React.ContextType<typeof ClientContext>;
constructor(props: Props) {
super(props);
this.state = {
directory_data: [],
- loading: true,
- loggedIn: undefined
+ loading: true
} as State;
}
- shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>, nextContext: React.ContextType<typeof ClientContext>) {
- if (typeof window !== "undefined" && (nextState.loggedIn === undefined || nextState.loggedIn !== !nextContext.client.isGuest)) {
- return true;
- }
- if (nextState !== this.state || nextContext !== this.context) {
- return true;
- }
- return false;
- }
-
componentDidUpdate(prevProps: Props, prevState: State) {
if (this.state.error && this.state.error !== prevState.error) {
toast(() => <div><h2 className="text-xl text-white">{i18n?.t("Error")}</h2><br />{this.state.error}</div>, {
@@ -59,7 +47,8 @@ export default class Header extends Component<Props, State> {
if (this.state.loading) {
return <></>;
}
- const loggedIn = (typeof window === "undefined") ? undefined : this.context.client.isGuest !== undefined ? (!this.context.client.isGuest) : undefined;
+ const loggedIn = (typeof window === "undefined") ? undefined : !this.context.client.isGuest;
+ console.log("loggedIn:", loggedIn);
return (
<>
<header className='bg-[#f8f8f8] dark:bg-[#06070D] flex fixed top-0 left-0 right-0 lg:h-20 h-auto z-[100] items-center lg:flex-row flex-col shadow-black drop-shadow-xl'>
@@ -91,19 +80,19 @@ export default class Header extends Component<Props, State> {
</div>
<nav className='flex lg:flex-shrink-0 my-4'>
- {!loggedIn ? <span className='px-4 h-auto min-w-[1.5rem] flex items-center whitespace-nowrap cursor-pointer text-gray-900 dark:text-gray-200 font-medium brightness-100 hover:brightness-75 duration-200 ease-in-out transition-all'><Link href="/register">{i18n?.t('Join')}</Link></span> : undefined}
- {!loggedIn ? <span className='px-4 h-auto min-w-[1.5rem] flex items-center whitespace-nowrap cursor-pointer text-gray-900 dark:text-gray-200 font-medium brightness-100 hover:brightness-75 duration-200 ease-in-out transition-all'><Link href="/login">{i18n?.t('Log in')}</Link></span> : undefined}
- {loggedIn && this.state.directory_data.some(thing => thing.mxid == this.context.client.userId) ? <span className='px-4 h-auto min-w-[1.5rem] flex items-center whitespace-nowrap cursor-pointer text-gray-900 dark:text-gray-200 font-medium brightness-100 hover:brightness-75 duration-200 ease-in-out transition-all'><Link href={"/profile/" + encodeURIComponent(this.context.client.userId!)}>{i18n?.t('Profile')}</Link></span> : undefined}
- {loggedIn ? <span className='px-4 h-auto min-w-[1.5rem] flex items-center whitespace-nowrap cursor-pointer text-gray-900 dark:text-gray-200 font-medium brightness-100 hover:brightness-75 duration-200 ease-in-out transition-all'><Link href="/logout/">{i18n?.t('Logout')}</Link></span> : undefined}
+ {loggedIn !== undefined && !loggedIn ? <span className='px-4 h-auto min-w-[1.5rem] flex items-center whitespace-nowrap cursor-pointer text-gray-900 dark:text-gray-200 font-medium brightness-100 hover:brightness-75 duration-200 ease-in-out transition-all'><Link href="/register">{i18n?.t('Join')}</Link></span> : undefined}
+ {loggedIn !== undefined && !loggedIn ? <span className='px-4 h-auto min-w-[1.5rem] flex items-center whitespace-nowrap cursor-pointer text-gray-900 dark:text-gray-200 font-medium brightness-100 hover:brightness-75 duration-200 ease-in-out transition-all'><Link href="/login">{i18n?.t('Log in')}</Link></span> : undefined}
+ {loggedIn !== undefined && loggedIn && this.state.directory_data.some(thing => thing.mxid == this.context.client.userId) ? <span className='px-4 h-auto min-w-[1.5rem] flex items-center whitespace-nowrap cursor-pointer text-gray-900 dark:text-gray-200 font-medium brightness-100 hover:brightness-75 duration-200 ease-in-out transition-all'><Link href={"/profile/" + encodeURIComponent(this.context.client.userId!)}>{i18n?.t('Profile')}</Link></span> : undefined}
+ {loggedIn !== undefined && loggedIn ? <span className='px-4 h-auto min-w-[1.5rem] flex items-center whitespace-nowrap cursor-pointer text-gray-900 dark:text-gray-200 font-medium brightness-100 hover:brightness-75 duration-200 ease-in-out transition-all'><Link href="/logout/">{i18n?.t('Logout')}</Link></span> : undefined}
</nav>
</div>
- {loggedIn ? <span className='lg:opacity-100 opacity-0 inline-block bg-gray-900 dark:bg-gray-200 w-[1px] lg:h-7 h-0'></span> : <span className="mr-4"></span>}
+ {loggedIn !== undefined && loggedIn ? <span className='lg:opacity-100 opacity-0 inline-block bg-gray-900 dark:bg-gray-200 w-[1px] lg:h-7 h-0'></span> : <span className="mr-4"></span>}
<div className='relative lg:m-0'>
<div className='flex'>
{
this.state.directory_data.some(thing => thing.mxid == this.context.client.userId) ?
- (loggedIn ? <Link href="/submit"><a className='inline-flex justify-center items-center text-teal-400 hover:text-teal-200 bg-transparent relative h-14 min-w-[9.25rem] z-[2] cursor-pointer font-bold'>{i18n?.t('Submit')}</a></Link> : undefined) :
- (loggedIn ? <a className='inline-flex justify-center items-center text-teal-400 hover:text-teal-200 bg-transparent relative h-14 min-w-[9.25rem] z-[2] cursor-pointer font-bold'>{i18n?.t('Setup Account')}</a> : undefined)
+ (loggedIn !== undefined && loggedIn ? <Link href="/submit"><a className='inline-flex justify-center items-center text-teal-400 hover:text-teal-200 bg-transparent relative h-14 min-w-[9.25rem] z-[2] cursor-pointer font-bold'>{i18n?.t('Submit')}</a></Link> : undefined) :
+ (loggedIn !== undefined && loggedIn ? <a className='inline-flex justify-center items-center text-teal-400 hover:text-teal-200 bg-transparent relative h-14 min-w-[9.25rem] z-[2] cursor-pointer font-bold'>{i18n?.t('Setup Account')}</a> : undefined)
}
</div>
</div>
diff --git a/e2e/checkEnglishWorks.spec.ts b/e2e/checkEnglishWorks.spec.ts
@@ -8,12 +8,14 @@ test('test if we have english words', async ({ page, baseURL }) => {
page.on('console', msg => console.log(msg.text()));
// Go to http://localhost:3000/
await page.goto(baseURL || 'http://localhost:3000/');
+ await page.waitForLoadState("networkidle");
expect(await page.locator("text=Home").isVisible());
});
test('test if the header is there and english with correct state', async ({ page, baseURL }) => {
page.on('console', msg => console.log(msg.text()));
+ await page.waitForLoadState("networkidle");
// Go to http://localhost:3000/
await page.goto(baseURL || 'http://localhost:3000/');
diff --git a/e2e/navigateToDetails.spec.ts b/e2e/navigateToDetails.spec.ts
@@ -6,6 +6,7 @@ test.use({
test('test navigation to details', async ({ page, baseURL }) => {
page.on('console', msg => console.log(msg.text()));
+ await page.waitForLoadState("networkidle");
// Go to http://localhost:3000/
await page.goto(baseURL || "http://localhost:3000");
@@ -14,11 +15,11 @@ test('test navigation to details', async ({ page, baseURL }) => {
// Click text=Flowers@mtrnord:art.midnightthoughts.space
await Promise.all([
page.waitForNavigation(/*{ url: 'http://localhost:3000/post/%24ugLG5srr5AyYCIhL1CnD6KikH8QYsDVMUHQ9jQRn990' }*/),
- page.click('text=Flowers')
+ page.click('text=Test')
]);
- await expect(page).toHaveURL(`${baseURL || "http://localhost:3000"}/post/%24xoIMe7tUMb2NhCBZaxsZr2CVkptCVu1GaJ_eJMKbJQo`);
+ await expect(page).toHaveURL(`${baseURL || "http://localhost:3000"}/post/%24QtbB-3JYAEOXJeC-mrZqFIEqon4uBLYVwTSw2SDWrJg`);
// Click svg
diff --git a/helpers/matrix_client.ts b/helpers/matrix_client.ts
@@ -20,13 +20,11 @@ export default class MatrixClient {
get accessToken(): string | undefined {
return this._accessToken;
}
- get isGuest(): boolean | undefined {
- return this._isGuest;
+ get isGuest(): boolean {
+ return this._isGuest === undefined ? true : this._isGuest;
}
get profileRoomId(): string | undefined {
- console.log(this.joinedRooms);
- console.log(this._profileRoomId);
return this.joinedRooms.get(`#${this.userId}`) || this._profileRoomId;
}
@@ -50,10 +48,10 @@ export default class MatrixClient {
return;
}
this.storage.setOrDelete("serverUrl", this.serverUrl);
- this.storage.setOrDelete("userId", this.userId);
- this.storage.setOrDelete("accessToken", this.accessToken);
+ this.storage.setOrDelete("userId", this._userId);
+ this.storage.setOrDelete("accessToken", this._accessToken);
this.storage.setOrDelete("serverName", this.serverName);
- this.storage.setOrDelete("isGuest", this.isGuest);
+ this.storage.setOrDelete("isGuest", this._isGuest);
}
private generateToken(len: number) {
@@ -75,20 +73,28 @@ export default class MatrixClient {
let password = this.generateToken(32);
let data;
- try {
- data = await this.fetchJson(`${serverUrl}/r0/register?kind=guest`, {
- method: "POST",
- body: JSON.stringify({
- auth: {
- type: "m.login.dummy",
- },
- username: username,
- password: password,
- }),
- });
- } catch (error) {
- console.error(`${serverUrl}/r0/register?kind=guest:\n${error}`);
- throw new Error("Failed to register new guest");
+ if (process.env.NEXT_PUBLIC_ENV !== "test") {
+ try {
+ data = await this.fetchJson(`${serverUrl}/r0/register?kind=guest`, {
+ method: "POST",
+ body: JSON.stringify({
+ auth: {
+ type: "m.login.dummy",
+ },
+ username: username,
+ password: password,
+ }),
+ });
+ } catch (error) {
+ console.error(`${serverUrl}/r0/register?kind=guest:\n${error}`);
+ throw new Error("Failed to register new guest");
+ }
+ } else {
+ data = {
+ user_id: "1",
+ access_token: "1",
+ home_server: "https://blub",
+ };
}
this.serverUrl = serverUrl;
this._userId = data.user_id;
@@ -120,6 +126,11 @@ export default class MatrixClient {
}
private async fetchJson(fullUrl: string, fetchParams: any) {
+ // Do not execute requests in tests!
+ if (process.env.NEXT_PUBLIC_ENV === "test") {
+ return;
+ }
+
const response = await fetch(fullUrl, fetchParams);
const data = await response.json();
if (response.status === 429) {
@@ -167,7 +178,7 @@ export default class MatrixClient {
body: "{}",
headers: { Authorization: `Bearer ${this.accessToken}` },
});
- return data.access_token;
+ return process.env.NEXT_PUBLIC_ENV !== "test" ? data.access_token : "openid";
}
async register(serverUrl: string, username: string, password: string) {
@@ -200,6 +211,17 @@ export default class MatrixClient {
}
async setDisplayname(newDisplayname: string) {
+ if (process.env.NEXT_PUBLIC_ENV === "test") {
+ if (this.userProfileCache.has(this.userId!)) {
+ const old = this.userProfileCache.get(this.userId!);
+ old.displayname = newDisplayname;
+ this.userProfileCache.set(this.userId!, old);
+ } else {
+ await this.getProfile(this.userId!);
+ }
+ return;
+ }
+
await this.fetchJson(
`${this.serverUrl}/r0/profile/${encodeURIComponent(this.userId!)}/displayname`,
{
@@ -218,6 +240,17 @@ export default class MatrixClient {
}
async setAvatarUrl(newAvatarUrl: string) {
+ if (process.env.NEXT_PUBLIC_ENV === "test") {
+ if (this.userProfileCache.has(this.userId!)) {
+ const old = this.userProfileCache.get(this.userId!);
+ old.avatar_url = newAvatarUrl;
+ this.userProfileCache.set(this.userId!, old);
+ } else {
+ await this.getProfile(this.userId!);
+ }
+ return;
+ }
+
await this.fetchJson(
`${this.serverUrl}/r0/profile/${encodeURIComponent(this.userId!)}/avatar_url`,
{
@@ -235,7 +268,14 @@ export default class MatrixClient {
}
}
- async getProfile(userId: string) {
+ async getProfile(userId: string): Promise<{ displayname?: string; avatar_url?: string; }> {
+ if (process.env.NEXT_PUBLIC_ENV === "test") {
+ const data = {
+ displayname: "Test"
+ };
+ this.userProfileCache.set(userId, data);
+ return data;
+ }
if (this.userProfileCache.has(userId)) {
console.debug(`Returning cached copy of ${userId}'s profile`);
return this.userProfileCache.get(userId);
@@ -258,7 +298,10 @@ export default class MatrixClient {
* @param {string} roomAlias The room alias to join
* @returns {string} The room ID of the joined room.
*/
- async joinProfileRoom(roomAlias: string) {
+ async joinProfileRoom(roomAlias: string): Promise<string> {
+ if (process.env.NEXT_PUBLIC_ENV === "test") {
+ return "!1:blub";
+ }
const roomId = this.joinedRooms.get(roomAlias);
if (roomId) {
return roomId;
@@ -352,6 +395,9 @@ export default class MatrixClient {
}
async sendEvent(roomId: string, event_type: string, content: MatrixContents): Promise<string> {
+ if (process.env.NEXT_PUBLIC_ENV === "test") {
+ return "$abcde";
+ }
const txnId = Date.now();
const data = await this.fetchJson(
`${this.serverUrl}/r0/rooms/${encodeURIComponent(
@@ -367,6 +413,9 @@ export default class MatrixClient {
}
async uploadFile(file: File | Blob): Promise<string> {
+ if (process.env.NEXT_PUBLIC_ENV === "test") {
+ return "mxc://blub/blub";
+ }
const fileName = (file as File).name || Date.now();
const mediaUrl = this.serverUrl?.slice(0, -1 * "/client".length);
const res = await fetch(
@@ -456,7 +505,47 @@ export default class MatrixClient {
return info;
}
- async getTimeline(roomId: string, limit: number, filter: object = { limit: 30, types: ["m.image", "m.image_gallery"] }) {// eslint-disable-line unicorn/no-object-as-default-parameter
+ async getTimeline(roomId: string, limit: number, filter: object = { limit: 30, types: ["m.image", "m.image_gallery"] }): Promise<MatrixEvent[]> {// eslint-disable-line unicorn/no-object-as-default-parameter
+ if (process.env.NEXT_PUBLIC_ENV === "test") {
+ return [
+ {
+ type: "m.image",
+ room_id: '!hxnnsrGMUfcrXPEPZu:art.midnightthoughts.space',
+ event_id: '$QtbB-3JYAEOXJeC-mrZqFIEqon4uBLYVwTSw2SDWrJg',
+ origin_server_ts: 1643317935965,
+ content: {
+ "m.caption": [
+ {
+ "m.text": "Test2"
+ }
+ ],
+ "m.thumbnail": [
+ {
+ "height": 500,
+ "mimetype": "image/jpeg",
+ "size": 126199,
+ "url": "mxc://art.midnightthoughts.space/b73141659586a6d690fe2c3edd43776b19f51529",
+ "width": 800
+ }
+ ],
+ 'm.file': {
+ mimetype: 'image/png',
+ name: '767-F_xp11 - 2021-04-13 20.23.07.png',
+ size: 2129309,
+ url: 'mxc://art.midnightthoughts.space/21a2b8190b756259510617d3c4c662d1e7c82141'
+ },
+ 'm.image': { height: 1040, width: 1920 },
+ 'm.text': 'Test',
+ 'matrixart.description': 'test2',
+ 'matrixart.license': 'cc-by-4.0',
+ 'matrixart.nsfw': false,
+ 'matrixart.tags': ['test', 'test2'],
+ 'xyz.amorgan.blurhash': 'LJA0tGWES2oL~pWDayj[.8WBsAbH'
+ },
+ sender: '@test:art.midnightthoughts.space'
+ }
+ ];
+ }
if (!this.accessToken) {
console.error("No access token");
return [];
diff --git a/package.json b/package.json
@@ -5,14 +5,13 @@
"cover 99.9%"
],
"scripts": {
- "dev-inspect": "cross-env NODE_OPTIONS='--inspect' next dev",
- "dev-tests": "cross-env PLAYWRIGHT='1' next dev",
"dev": "next dev",
- "build": "next build",
- "start": "next start",
- "export": "next export",
- "lint": "next lint",
- "extractKeys": "i18next 'pages/**/*.{ts,tsx}' 'components/**/*.{ts,tsx}'"
+ "build": "cross-env NODE_ENV='production' next build",
+ "start": "cross-env NODE_ENV='production' next start",
+ "export": "cross-env NODE_ENV='production' next export",
+ "lint": "cross-env NODE_ENV='production' next lint",
+ "extractKeys": "i18next 'pages/**/*.{ts,tsx}' 'components/**/*.{ts,tsx}'",
+ "test": "cross-env NODE_ENV='test' playwright test"
},
"engines": {
"node": ">=17.0.0"
diff --git a/pages/api/directory.ts b/pages/api/directory.ts
@@ -22,12 +22,11 @@ const db = new Sequelize({
db.addModels([User]);
export const get_data = async () => {
- if (process.env.PLAYWRIGHT === '1') {
- console.log("Running in tests!");
+ if (process.env.NEXT_PUBLIC_ENV === "test") {
return [
new User({
- "mxid": "@mtrnord:art.midnightthoughts.space",
- "public_user_room": "#@mtrnord:art.midnightthoughts.space"
+ "mxid": "@test:art.midnightthoughts.space",
+ "public_user_room": "#@test:art.midnightthoughts.space"
})
];
}
@@ -66,7 +65,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
user_room: string;
access_token: string;
} = JSON.parse(req.body);
- if (await (new ServerOpenID().verify(data.user_id, data.access_token))) {
+ if (process.env.NEXT_PUBLIC_ENV === "test" ? data.access_token === "openid" : await (new ServerOpenID().verify(data.user_id, data.access_token))) {
try {
await User.sync();
await User.create({
@@ -89,7 +88,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
user_id: string;
access_token: string;
} = JSON.parse(req.body);
- if (await (new ServerOpenID().verify(data.user_id, data.access_token))) {
+ if (process.env.NEXT_PUBLIC_ENV === "test" ? data.access_token === "openid" : await (new ServerOpenID().verify(data.user_id, data.access_token))) {
try {
await User.sync();
await User.destroy({
diff --git a/pages/post/[id].tsx b/pages/post/[id].tsx
@@ -114,7 +114,7 @@ class Post extends PureComponent<Props, State> {
const profile = await client.getProfile(image_event.sender);
this.setState({
image_event: image_event as MatrixImageEvents,
- displayname: profile.displayname,
+ displayname: profile.displayname || image_event.sender,
});
} catch (error) {
@@ -491,6 +491,7 @@ export const getServerSideProps: GetServerSideProps = async ({ res, locale, quer
roomId = await client?.followUser(user.public_user_room);
} catch {
console.error("Unbable to join room");
+ continue;
}
const events = await client?.getTimeline(roomId, 100);
// Filter events by type
diff --git a/pages/profile/[userid].tsx b/pages/profile/[userid].tsx
@@ -22,7 +22,7 @@ type Props = InferGetServerSidePropsType<typeof getServerSideProps> & {
type State = {
displayname: string;
- avatar_url: string;
+ avatar_url?: string;
events: MatrixEvent[] | [];
profile_event?: MatrixArtProfile;
error?: string;
@@ -41,7 +41,6 @@ class Profile extends PureComponent<Props, State> {
this.state = {
displayname: this.props.mxid,
- avatar_url: "",
events: [],
isLoadingImages: false,
hasFullyLoaded: false,
@@ -83,7 +82,7 @@ class Profile extends PureComponent<Props, State> {
try {
const profile = await this.context.client.getProfile(this.props.mxid);
this.setState({
- displayname: profile.displayname,
+ displayname: profile.displayname || this.props.event.sender,
avatar_url: profile.avatar_url,
});
} catch (error) {
diff --git a/playwright.config.ts b/playwright.config.ts
@@ -106,7 +106,7 @@ const config: PlaywrightTestConfig = {
/* Run your local dev server before starting the tests */
webServer: {
- command: 'npm run dev-tests',
+ command: 'npm run dev',
port: 3000,
},
};