mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-05 15:55:56 +02:00
enhance(frontend): niraxにテストを追加 (#17287)
* fix(frontend): follow-up of #13509 * fix: fix use of inappropriate method * enhance(frontend): niraxにテストを追加
This commit is contained in:
90
packages/frontend/test/lib/nirax/fallbacks.test.ts
Normal file
90
packages/frontend/test/lib/nirax/fallbacks.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { assert, describe, test } from 'vitest';
|
||||
import { createRouter, loginFallbackComponent } from './fixture.js';
|
||||
|
||||
describe('[NIRAX] フォールバック', () => {
|
||||
test('pushの際、ページが見つからなかったらforcePushを発火する', () => {
|
||||
const router = createRouter('/');
|
||||
const forcePushes: string[] = [];
|
||||
|
||||
router.addListener('forcePush', ctx => {
|
||||
forcePushes.push(ctx.fullPath);
|
||||
assert.strictEqual(ctx.onInit, false);
|
||||
});
|
||||
|
||||
router.init();
|
||||
|
||||
router.pushByPath('/missing');
|
||||
|
||||
assert.deepStrictEqual(forcePushes, ['/missing']);
|
||||
assert.strictEqual(router.getCurrentFullPath(), '/');
|
||||
assert.strictEqual(router.current.route.path, '/');
|
||||
});
|
||||
|
||||
test('replaceの際、ページが見つからなかったらforceReplaceを発火する', () => {
|
||||
const router = createRouter('/');
|
||||
const forceReplacements: string[] = [];
|
||||
|
||||
router.addListener('forceReplace', ctx => {
|
||||
forceReplacements.push(ctx.fullPath);
|
||||
assert.strictEqual(ctx.onInit, false);
|
||||
});
|
||||
|
||||
router.init();
|
||||
|
||||
router.replaceByPath('/also-missing');
|
||||
|
||||
assert.deepStrictEqual(forceReplacements, ['/also-missing']);
|
||||
assert.strictEqual(router.getCurrentFullPath(), '/');
|
||||
assert.strictEqual(router.current.route.path, '/');
|
||||
});
|
||||
|
||||
test('初期ページが見つからない場合でも初回はforceReplaceを発火しない', () => {
|
||||
const router = createRouter('/missing');
|
||||
const forceReplacements: string[] = [];
|
||||
|
||||
router.addListener('forceReplace', ctx => {
|
||||
forceReplacements.push(ctx.fullPath);
|
||||
assert.strictEqual(ctx.onInit, true);
|
||||
});
|
||||
|
||||
router.init();
|
||||
|
||||
assert.deepStrictEqual(forceReplacements, []); // 初回はforceReplaceを発火しない
|
||||
assert.strictEqual(router.getCurrentFullPath(), '/missing');
|
||||
assert.strictEqual(router.current.route.path, '/:(*)');
|
||||
});
|
||||
|
||||
test('初期ページが見つからない場合でも、initで明示した場合はforceReplaceを発火する', () => {
|
||||
const router = createRouter('/missing');
|
||||
const forceReplacements: string[] = [];
|
||||
|
||||
router.addListener('forceReplace', ctx => {
|
||||
forceReplacements.push(ctx.fullPath);
|
||||
assert.strictEqual(ctx.onInit, true);
|
||||
});
|
||||
|
||||
router.init(true); // forceReplaceを強制的に発火させる
|
||||
|
||||
assert.deepStrictEqual(forceReplacements, ['/missing']);
|
||||
assert.strictEqual(router.getCurrentFullPath(), '/missing');
|
||||
assert.strictEqual(router.current.route.path, '/:(*)');
|
||||
});
|
||||
|
||||
test('loginRequiredなルートではコンポーネントを差し替えてshowLoginPopupを設定する', () => {
|
||||
const router = createRouter('/', false);
|
||||
|
||||
router.init();
|
||||
|
||||
router.pushByPath('/private');
|
||||
|
||||
assert.strictEqual(router.current.route.path, '/private');
|
||||
assert.ok('component' in router.current.route);
|
||||
assert.strictEqual(router.current.route.component, loginFallbackComponent);
|
||||
assert.strictEqual(router.current.props.get('showLoginPopup'), true);
|
||||
});
|
||||
});
|
||||
80
packages/frontend/test/lib/nirax/fixture.ts
Normal file
80
packages/frontend/test/lib/nirax/fixture.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Component } from 'vue';
|
||||
import { Nirax } from '@/lib/nirax.js';
|
||||
import type { RouteDef } from '@/lib/nirax.js';
|
||||
|
||||
export const homeComponent = { name: 'home-page' } as Component;
|
||||
export const postComponent = { name: 'post-page' } as Component;
|
||||
export const fileComponent = { name: 'file-page' } as Component;
|
||||
export const optionalComponent = { name: 'optional-page' } as Component;
|
||||
export const userComponent = { name: 'user-page' } as Component;
|
||||
export const followersComponent = { name: 'followers-page' } as Component;
|
||||
export const privateComponent = { name: 'private-page' } as Component;
|
||||
export const notFoundRouteComponent = { name: 'not-found-route' } as Component;
|
||||
export const loginFallbackComponent = { name: 'login-fallback' } as Component;
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: homeComponent,
|
||||
},
|
||||
{
|
||||
path: '/posts/:postId',
|
||||
component: postComponent,
|
||||
query: {
|
||||
from: 'source',
|
||||
},
|
||||
hash: 'section',
|
||||
},
|
||||
{
|
||||
path: '/files/:path(*)',
|
||||
component: fileComponent,
|
||||
},
|
||||
{
|
||||
path: '/optional/:slug?',
|
||||
component: optionalComponent,
|
||||
},
|
||||
{
|
||||
path: '/old',
|
||||
redirect: '/posts/redirected',
|
||||
},
|
||||
{
|
||||
path: '/legacy/:postId',
|
||||
redirect: props => `/posts/${props.get('postId')}`,
|
||||
},
|
||||
{
|
||||
path: '/loop-a',
|
||||
redirect: '/loop-b',
|
||||
},
|
||||
{
|
||||
path: '/loop-b',
|
||||
redirect: '/loop-a',
|
||||
},
|
||||
{
|
||||
path: '/user/:id',
|
||||
component: userComponent,
|
||||
children: [
|
||||
{
|
||||
path: '/followers',
|
||||
component: followersComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/private',
|
||||
component: privateComponent,
|
||||
loginRequired: true,
|
||||
},
|
||||
{
|
||||
path: '/:(*)',
|
||||
component: notFoundRouteComponent,
|
||||
},
|
||||
] as const satisfies RouteDef[];
|
||||
|
||||
export function createRouter(currentFullPath = '/', isLoggedIn = true) {
|
||||
return new Nirax(routes, currentFullPath, isLoggedIn, loginFallbackComponent);
|
||||
}
|
||||
105
packages/frontend/test/lib/nirax/navigation.test.ts
Normal file
105
packages/frontend/test/lib/nirax/navigation.test.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { assert, describe, test } from 'vitest';
|
||||
import { createRouter } from './fixture.js';
|
||||
|
||||
describe('[NIRAX] ナビゲーションイベント', () => {
|
||||
test('init時にリダイレクトを解決してreplaceを発火する', () => {
|
||||
const router = createRouter('/old?from=legacy#intro');
|
||||
const changes: string[] = [];
|
||||
const replacements: string[] = [];
|
||||
|
||||
router.addListener('change', ctx => {
|
||||
changes.push(ctx.fullPath);
|
||||
});
|
||||
router.addListener('replace', ctx => {
|
||||
replacements.push(ctx.fullPath);
|
||||
});
|
||||
|
||||
router.init();
|
||||
|
||||
assert.strictEqual(router.getCurrentFullPath(), '/posts/redirected?from=legacy#intro');
|
||||
assert.strictEqual(router.current.redirected, true);
|
||||
assert.deepStrictEqual(changes, []); // 初回はchangeを発火しない
|
||||
assert.deepStrictEqual(replacements, ['/posts/redirected?from=legacy#intro']);
|
||||
});
|
||||
|
||||
test('push時に動的リダイレクトを解決してpushとchangeを発火する', () => {
|
||||
const router = createRouter('/');
|
||||
const pushed: string[] = [];
|
||||
const changed: string[] = [];
|
||||
|
||||
router.addListener('push', ctx => {
|
||||
pushed.push(ctx.fullPath);
|
||||
assert.strictEqual(ctx.route?.path, '/posts/:postId');
|
||||
assert.strictEqual(ctx.props?.get('postId'), 'abc123');
|
||||
});
|
||||
router.addListener('change', ctx => {
|
||||
changed.push(ctx.fullPath);
|
||||
});
|
||||
|
||||
router.init();
|
||||
|
||||
router.pushByPath('/legacy/abc123');
|
||||
|
||||
assert.strictEqual(router.getCurrentFullPath(), '/posts/abc123');
|
||||
assert.deepStrictEqual(pushed, ['/posts/abc123']);
|
||||
assert.deepStrictEqual(changed, ['/posts/abc123']);
|
||||
});
|
||||
|
||||
test('無限リダイレクトはエラーになる', () => {
|
||||
const router = createRouter('/');
|
||||
|
||||
router.init();
|
||||
|
||||
assert.throws(() => {
|
||||
router.pushByPath('/loop-a');
|
||||
}, /redirect loop detected/);
|
||||
assert.strictEqual(router.getCurrentFullPath(), '/');
|
||||
});
|
||||
|
||||
test('同じパスへの遷移ではsameを発火する', () => {
|
||||
const router = createRouter('/posts/123');
|
||||
let sameCount = 0;
|
||||
let pushCount = 0;
|
||||
|
||||
router.addListener('same', () => {
|
||||
sameCount++;
|
||||
});
|
||||
router.addListener('push', () => {
|
||||
pushCount++;
|
||||
});
|
||||
|
||||
router.init();
|
||||
|
||||
router.pushByPath('/posts/123');
|
||||
|
||||
assert.strictEqual(sameCount, 1);
|
||||
assert.strictEqual(pushCount, 0); // sameのときはpushを発火しない
|
||||
});
|
||||
|
||||
test('navHookでナビゲーションをキャンセルできる', () => {
|
||||
const router = createRouter('/posts/123');
|
||||
const navHookCalls: string[] = [];
|
||||
let pushCount = 0;
|
||||
|
||||
router.addListener('push', () => {
|
||||
pushCount++;
|
||||
});
|
||||
router.navHook = fullPath => {
|
||||
navHookCalls.push(fullPath);
|
||||
return true;
|
||||
};
|
||||
|
||||
router.init();
|
||||
|
||||
router.pushByPath('/posts/456');
|
||||
|
||||
assert.deepStrictEqual(navHookCalls, ['/posts/456']);
|
||||
assert.strictEqual(pushCount, 0);
|
||||
assert.strictEqual(router.getCurrentFullPath(), '/posts/123');
|
||||
});
|
||||
});
|
||||
73
packages/frontend/test/lib/nirax/resolve.test.ts
Normal file
73
packages/frontend/test/lib/nirax/resolve.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { assert, describe, test } from 'vitest';
|
||||
import { createRouter } from './fixture.js';
|
||||
|
||||
describe('[NIRAX] resolve', () => {
|
||||
test('staticなルートを解決できる', () => {
|
||||
const router = createRouter();
|
||||
const resolved = router.resolve('/');
|
||||
|
||||
assert.ok(resolved);
|
||||
assert.strictEqual(resolved.route.path, '/');
|
||||
assert.strictEqual(resolved.props.size, 0);
|
||||
});
|
||||
|
||||
test('パスパラメータ付きルートを解決できる', () => {
|
||||
const router = createRouter();
|
||||
const resolved = router.resolve('/posts/abc%2Fdef');
|
||||
|
||||
assert.ok(resolved);
|
||||
assert.strictEqual(resolved.route.path, '/posts/:postId');
|
||||
assert.strictEqual(resolved.props.get('postId'), 'abc/def');
|
||||
});
|
||||
|
||||
test('queryとhashのエイリアスを解決できる', () => {
|
||||
const router = createRouter();
|
||||
const resolved = router.resolve('/posts/abc?from=timeline#thread');
|
||||
|
||||
assert.ok(resolved);
|
||||
assert.strictEqual(resolved.props.get('source'), 'timeline');
|
||||
assert.strictEqual(resolved.props.get('section'), 'thread');
|
||||
});
|
||||
|
||||
test('wildcardルートのパラメータを解決できる', () => {
|
||||
const router = createRouter();
|
||||
const resolved = router.resolve('/files/images/icons/logo%20mark.svg');
|
||||
|
||||
assert.ok(resolved);
|
||||
assert.strictEqual(resolved.route.path, '/files/:path(*)');
|
||||
assert.strictEqual(resolved.props.get('path'), 'images/icons/logo mark.svg');
|
||||
});
|
||||
|
||||
test('optionalなパスパラメータが省略されたルートを解決できる', () => {
|
||||
const router = createRouter();
|
||||
const resolved = router.resolve('/optional');
|
||||
|
||||
assert.ok(resolved);
|
||||
assert.strictEqual(resolved.route.path, '/optional/:slug?');
|
||||
assert.strictEqual(resolved.props.has('slug'), false);
|
||||
});
|
||||
|
||||
test('optionalなパスパラメータが存在するルートを解決できる', () => {
|
||||
const router = createRouter();
|
||||
const resolved = router.resolve('/optional/topic');
|
||||
|
||||
assert.ok(resolved);
|
||||
assert.strictEqual(resolved.props.get('slug'), 'topic');
|
||||
});
|
||||
|
||||
test('ネストされたルートを解決できる', () => {
|
||||
const router = createRouter();
|
||||
const resolved = router.resolve('/user/alice/followers');
|
||||
|
||||
assert.ok(resolved);
|
||||
assert.strictEqual(resolved.route.path, '/user/:id');
|
||||
assert.strictEqual(resolved.props.get('id'), 'alice');
|
||||
assert.ok(resolved.child);
|
||||
assert.strictEqual(resolved.child.route.path, '/followers');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user