@nocobase/app-template-default 1.0.0-beta.87 → 1.0.0-beta.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/rules/components.md +3 -1
- package/.agents/rules/notification-templates.md +5 -0
- package/.agents/rules/shell.md +9 -3
- package/AGENTS.md +4 -2
- package/README.MD +11 -5
- package/client/components/back-button.tsx +37 -12
- package/client/components/body-portal.tsx +25 -0
- package/client/components/brand-loading-mark.tsx +5 -2
- package/client/components/brand-mark-geometry.ts +3 -3
- package/client/components/breadcrumbs.tsx +60 -3
- package/client/components/data-table/data-table.tsx +90 -19
- package/client/components/data-table/pagination.tsx +17 -2
- package/client/components/flow-diagram.tsx +2 -2
- package/client/components/list-feedback.tsx +31 -22
- package/client/components/loading.tsx +127 -16
- package/client/components/pending-button.tsx +111 -0
- package/client/components/role-list.tsx +7 -0
- package/client/components/route-child-page.tsx +72 -1
- package/client/components/route-overlay.tsx +149 -15
- package/client/components/task-progress.tsx +155 -0
- package/client/components/use-route-overlay.ts +8 -11
- package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +62 -32
- package/client/extensions/nocobase-file-component-ui/locales/de-DE.ts +2 -0
- package/client/extensions/nocobase-file-component-ui/locales/en-US.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/es-ES.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/fr-FR.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/id-ID.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/ja-JP.ts +2 -0
- package/client/extensions/nocobase-file-component-ui/locales/pt-BR.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/vi-VN.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/zh-CN.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/zh-TW.ts +1 -0
- package/client/extensions/nocobase-workflow-management/README.md +5 -5
- package/client/extensions/nocobase-workflow-management/definition-list.tsx +2 -9
- package/client/extensions/nocobase-workflow-management/execution-list.tsx +2 -4
- package/client/layouts/app-layout.tsx +8 -8
- package/client/layouts/components/sidebar-footer.tsx +18 -25
- package/client/lib/navigation-transform.ts +156 -22
- package/client/locales/system/en-US.ts +26 -5
- package/client/locales/system/settings-en-US.ts +17 -0
- package/client/locales/system/settings-zh-CN.ts +15 -0
- package/client/locales/system/zh-CN.ts +26 -5
- package/client/pages/auth/register.tsx +39 -22
- package/client/pages/auth/shared.tsx +1 -3
- package/client/pages/settings/default-access.tsx +1 -1
- package/client/pages/settings/hooks.tsx +3 -68
- package/client/pages/settings/notification-routes.ts +109 -0
- package/client/pages/settings/notification-templates.tsx +26 -0
- package/client/pages/settings/permission-management-page.tsx +15 -90
- package/client/pages/settings/roles.tsx +27 -27
- package/client/pages/settings/routes.ts +107 -101
- package/client/pages/settings/settings-home.tsx +109 -0
- package/client/pages/settings/settings-module-page.tsx +158 -0
- package/client/pages/settings/shell-routes.ts +53 -0
- package/client/pages/settings/user-detail.tsx +3 -17
- package/client/pages/settings/user-form.tsx +9 -15
- package/client/pages/settings/users.tsx +11 -27
- package/client/routes.ts +17 -1
- package/client/routing/app-router.tsx +1 -14
- package/client/routing/client-route.tsx +199 -30
- package/client/routing/navigation-destination.ts +42 -0
- package/client/routing/route-context.tsx +30 -3
- package/client/routing/route-leave-boundary.tsx +261 -0
- package/client/routing/route-navigation.ts +72 -8
- package/client/routing/route-tree.tsx +7 -12
- package/client/runtime.ts +2 -2
- package/client/styles.css +18 -1
- package/config.example.yml +1 -1
- package/package.json +13 -13
- package/server/config/auth.ts +1 -1
- package/server/providers/index.ts +2 -0
- package/server/providers/notification-management.ts +27 -0
- package/tests/client/account-permissions.test.tsx +1 -1
- package/tests/client/auth-i18n.test.tsx +1 -1
- package/tests/client/authenticated-client-fixture.test.tsx +37 -0
- package/tests/client/back-button.test.tsx +34 -0
- package/tests/client/client-auth.test.tsx +10 -1
- package/tests/client/client-route-recovery.test.tsx +93 -1
- package/tests/client/client-routes.test.ts +137 -4
- package/tests/client/client-shell.test.tsx +23 -8
- package/tests/client/foundation-integration.test.ts +38 -17
- package/tests/client/layout-i18n.test.tsx +9 -4
- package/tests/client/list-feedback.test.tsx +5 -5
- package/tests/client/list-loading.test.tsx +397 -0
- package/tests/client/loading.test.tsx +20 -11
- package/tests/client/manual-list-retry.test.tsx +2 -2
- package/tests/client/navigation-parent.test.tsx +172 -0
- package/tests/client/pending-button.test.tsx +106 -0
- package/tests/client/permission-management-tabs.test.tsx +458 -17
- package/tests/client/primitive-labels.test.tsx +2 -2
- package/tests/client/registration-feedback.test.tsx +143 -0
- package/tests/client/role-return.test.tsx +21 -3
- package/tests/client/roles.test.tsx +84 -3
- package/tests/client/route-leave-recheck.test.tsx +74 -0
- package/tests/client/route-presentation.test.tsx +557 -0
- package/tests/client/session-preparation.test.tsx +20 -17
- package/tests/client/settings-continuity.test.tsx +35 -11
- package/tests/client/settings-home.test.tsx +112 -0
- package/tests/client/settings-idle-list.test.tsx +124 -0
- package/tests/client/settings-navigation.test.ts +230 -0
- package/tests/client/user-drawer.test.tsx +3 -4
- package/tests/fixtures/dialog-footer.html +12 -0
- package/tests/fixtures/dialog-footer.tsx +215 -0
- package/tests/helpers/authenticated-client.tsx +70 -22
- package/tests/playwright/dialog-footer.test.ts +227 -0
- package/tests/server/notification-templates.test.ts +144 -0
- package/tests/client/permission-management-routes.test.ts +0 -64
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthenticationProvider,
|
|
3
|
+
GuestAuthentication,
|
|
4
|
+
authenticationClientToken,
|
|
5
|
+
} from '@nocobase/app-plugin-authentication/client';
|
|
6
|
+
import { authorizationClientToken } from '@nocobase/app-plugin-authorization/client';
|
|
7
|
+
import { reactProviders } from '@nocobase/app-plugin-authorization/client/react-providers';
|
|
8
|
+
import { renderWithApp } from '@nocobase/app-testing/client';
|
|
9
|
+
import { act, fireEvent, screen } from '@testing-library/react';
|
|
10
|
+
import { Route, Routes } from 'react-router';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import enUS from '../../client/locales/en-US.js';
|
|
13
|
+
import zhCN from '../../client/locales/zh-CN.js';
|
|
14
|
+
import RegisterPage from '../../client/pages/auth/register.js';
|
|
15
|
+
|
|
16
|
+
const AuthorizationProvider = reactProviders[0]!.component;
|
|
17
|
+
|
|
18
|
+
async function renderRegistration(signedIn = false, locale = 'en-US') {
|
|
19
|
+
let registered = false;
|
|
20
|
+
const refresh = Promise.withResolvers<void>();
|
|
21
|
+
const email = vi.fn(async () => {
|
|
22
|
+
registered = true;
|
|
23
|
+
return { token: null, user: { id: 'untrusted-id' } };
|
|
24
|
+
});
|
|
25
|
+
const getSession = vi.fn(async () => {
|
|
26
|
+
if (registered) await refresh.promise;
|
|
27
|
+
return {
|
|
28
|
+
data:
|
|
29
|
+
registered && signedIn
|
|
30
|
+
? { user: { id: 'real-user' }, session: { id: 'real-session' } }
|
|
31
|
+
: null,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
const view = await renderWithApp(
|
|
35
|
+
<AuthenticationProvider>
|
|
36
|
+
<AuthorizationProvider>
|
|
37
|
+
<Routes>
|
|
38
|
+
<Route
|
|
39
|
+
path='/register'
|
|
40
|
+
element={
|
|
41
|
+
<GuestAuthentication>
|
|
42
|
+
<RegisterPage />
|
|
43
|
+
</GuestAuthentication>
|
|
44
|
+
}
|
|
45
|
+
/>
|
|
46
|
+
<Route path='/' element={<p>Signed-in home</p>} />
|
|
47
|
+
</Routes>
|
|
48
|
+
</AuthorizationProvider>
|
|
49
|
+
</AuthenticationProvider>,
|
|
50
|
+
{
|
|
51
|
+
route: '/register',
|
|
52
|
+
locale,
|
|
53
|
+
namespace: 'registration-test',
|
|
54
|
+
namespaces: { 'registration-test': locale === 'en-US' ? enUS : zhCN },
|
|
55
|
+
services: (app) => {
|
|
56
|
+
app.container.instance(authorizationClientToken, {
|
|
57
|
+
resetSession: vi.fn(),
|
|
58
|
+
});
|
|
59
|
+
app.container.instance(authenticationClientToken, {
|
|
60
|
+
getSession,
|
|
61
|
+
signUp: { email },
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
await screen.findByRole('heading', {
|
|
67
|
+
name: locale === 'en-US' ? 'Create an account' : '创建账户',
|
|
68
|
+
});
|
|
69
|
+
return { ...view, refresh, email };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function submitForm(container: HTMLElement, locale = 'en-US') {
|
|
73
|
+
const labels =
|
|
74
|
+
locale === 'en-US'
|
|
75
|
+
? ['Name', 'Username', 'Email', 'Password', 'Confirm password']
|
|
76
|
+
: ['姓名', '用户名', '邮箱', '密码', '确认密码'];
|
|
77
|
+
const values = [
|
|
78
|
+
'Person',
|
|
79
|
+
'person',
|
|
80
|
+
'person@example.test',
|
|
81
|
+
'test-password',
|
|
82
|
+
'test-password',
|
|
83
|
+
];
|
|
84
|
+
labels.forEach((label, index) =>
|
|
85
|
+
fireEvent.change(screen.getByLabelText(label), {
|
|
86
|
+
target: { value: values[index] },
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
fireEvent.submit(container.querySelector('form')!);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
describe('registration feedback with the real session provider and guest guard', () => {
|
|
93
|
+
it.each(['en-US', 'zh-CN'])(
|
|
94
|
+
'keeps accepted unsigned-in feedback after session refresh in %s',
|
|
95
|
+
async (locale) => {
|
|
96
|
+
const { container, refresh, email } = await renderRegistration(
|
|
97
|
+
false,
|
|
98
|
+
locale,
|
|
99
|
+
);
|
|
100
|
+
submitForm(container, locale);
|
|
101
|
+
try {
|
|
102
|
+
await vi.waitFor(() => expect(email).toHaveBeenCalledOnce());
|
|
103
|
+
expect(container.querySelector('form')).toBeInTheDocument();
|
|
104
|
+
} finally {
|
|
105
|
+
await act(async () => {
|
|
106
|
+
refresh.resolve();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const status = await screen.findByRole('status');
|
|
110
|
+
expect(status).toHaveTextContent(
|
|
111
|
+
locale === 'en-US'
|
|
112
|
+
? 'the new details will not take effect'
|
|
113
|
+
: '新填写的信息不会生效',
|
|
114
|
+
);
|
|
115
|
+
expect(container.querySelector('form')).not.toBeInTheDocument();
|
|
116
|
+
expect(
|
|
117
|
+
screen.getByRole('link', {
|
|
118
|
+
name: locale === 'en-US' ? 'Sign in' : '登录',
|
|
119
|
+
}),
|
|
120
|
+
).toHaveAttribute('href', '/login');
|
|
121
|
+
expect(
|
|
122
|
+
screen.getByRole('link', {
|
|
123
|
+
name: locale === 'en-US' ? 'Forgot password?' : '忘记密码?',
|
|
124
|
+
}),
|
|
125
|
+
).toHaveAttribute('href', '/forgot-password');
|
|
126
|
+
expect(status).not.toHaveTextContent(
|
|
127
|
+
/account (?:created|has been created)|email (?:sent|has been sent)|账户已创建|邮件已发送/i,
|
|
128
|
+
);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
it('uses the refreshed session to leave the guest page', async () => {
|
|
133
|
+
const { container, refresh } = await renderRegistration(true);
|
|
134
|
+
submitForm(container);
|
|
135
|
+
await act(async () => {
|
|
136
|
+
refresh.resolve();
|
|
137
|
+
});
|
|
138
|
+
expect(await screen.findByText('Signed-in home')).toBeVisible();
|
|
139
|
+
expect(
|
|
140
|
+
screen.queryByText('Registration request submitted'),
|
|
141
|
+
).not.toBeInTheDocument();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AppClientRegisteredRoute } from '@nocobase/app-client/plugins';
|
|
2
|
+
import { RouteTreeProvider } from '../../client/routing/route-context.js';
|
|
1
3
|
import { renderSettingsClient as renderWithApp } from '../helpers/settings-client.js';
|
|
2
4
|
import { answerApi } from '@nocobase/app-testing/client';
|
|
3
5
|
import { apiClientToken, type ClientApplication } from '@nocobase/app-client';
|
|
@@ -14,8 +16,24 @@ import enUS from '../../client/locales/en-US.js';
|
|
|
14
16
|
import zhCN from '../../client/locales/zh-CN.js';
|
|
15
17
|
|
|
16
18
|
const namespace = '@nocobase/role-return-test';
|
|
17
|
-
function Parent() {
|
|
18
|
-
|
|
19
|
+
function Parent({ path }: { path: string }) {
|
|
20
|
+
const routes: AppClientRegisteredRoute[] = [
|
|
21
|
+
{
|
|
22
|
+
id: 'roles',
|
|
23
|
+
name: 'roles',
|
|
24
|
+
path,
|
|
25
|
+
packageName: namespace,
|
|
26
|
+
source: 'application',
|
|
27
|
+
auth: 'required',
|
|
28
|
+
authz: 'skip',
|
|
29
|
+
componentLoader: async () => ({ default: () => null }),
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
return (
|
|
33
|
+
<RouteTreeProvider routes={routes}>
|
|
34
|
+
<Outlet context={{ reload: () => undefined }} />
|
|
35
|
+
</RouteTreeProvider>
|
|
36
|
+
);
|
|
19
37
|
}
|
|
20
38
|
function Location() {
|
|
21
39
|
const { pathname, search } = useLocation();
|
|
@@ -50,7 +68,7 @@ it.each(
|
|
|
50
68
|
const result = await renderWithApp(
|
|
51
69
|
<>
|
|
52
70
|
<Routes>
|
|
53
|
-
<Route path={parent} element={<Parent />}>
|
|
71
|
+
<Route path={parent} element={<Parent path={parent} />}>
|
|
54
72
|
<Route index element={<h1>Role list</h1>} />
|
|
55
73
|
<Route path=':key/edit' element={<RoleForm />} />
|
|
56
74
|
</Route>
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
AuthorizationClient,
|
|
5
5
|
authorizationClientToken,
|
|
6
6
|
} from '@nocobase/app-plugin-authorization/client';
|
|
7
|
-
import { screen, waitFor, within } from '@testing-library/react';
|
|
7
|
+
import { act, screen, waitFor, within } from '@testing-library/react';
|
|
8
8
|
import userEvent from '@testing-library/user-event';
|
|
9
|
-
import { Routes, Route } from 'react-router';
|
|
10
|
-
import { expect, it } from 'vitest';
|
|
9
|
+
import { Routes, Route, useNavigate } from 'react-router';
|
|
10
|
+
import { expect, it, vi } from 'vitest';
|
|
11
11
|
import RolesPage from '../../client/pages/settings/roles.js';
|
|
12
12
|
import enUS from '../../client/locales/en-US.js';
|
|
13
13
|
import zhCN from '../../client/locales/zh-CN.js';
|
|
@@ -83,3 +83,84 @@ it('keeps the standard role table sorted, searchable and linked to the current q
|
|
|
83
83
|
'/settings/roles/cp-customer/edit?q=Customer',
|
|
84
84
|
);
|
|
85
85
|
});
|
|
86
|
+
|
|
87
|
+
function Navigate({ to, label }: { to: string; label: string }) {
|
|
88
|
+
const navigate = useNavigate();
|
|
89
|
+
return (
|
|
90
|
+
<button type='button' onClick={() => void navigate(to)}>
|
|
91
|
+
{label}
|
|
92
|
+
</button>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
it('announces a slow retry after a refresh failure once, from the retry button', async () => {
|
|
97
|
+
const user = userEvent.setup();
|
|
98
|
+
const roles = { data: [{ key: 'team', title: 'NocoBase team', grants: [] }] };
|
|
99
|
+
let reads = 0;
|
|
100
|
+
let finish!: () => void;
|
|
101
|
+
const slow = new Promise<void>((resolve) => {
|
|
102
|
+
finish = resolve;
|
|
103
|
+
});
|
|
104
|
+
await renderWithApp(
|
|
105
|
+
<>
|
|
106
|
+
<Routes>
|
|
107
|
+
<Route path='/settings/roles' element={<RolesPage />}>
|
|
108
|
+
<Route
|
|
109
|
+
path='covered'
|
|
110
|
+
element={<Navigate to='/settings/roles' label='Back to roles' />}
|
|
111
|
+
/>
|
|
112
|
+
</Route>
|
|
113
|
+
</Routes>
|
|
114
|
+
<Navigate to='/settings/roles/covered' label='Cover roles' />
|
|
115
|
+
</>,
|
|
116
|
+
{
|
|
117
|
+
route: '/settings/roles',
|
|
118
|
+
namespace: settingsNamespace,
|
|
119
|
+
namespaces,
|
|
120
|
+
services,
|
|
121
|
+
fetch: answerApi(async ({ path }) => {
|
|
122
|
+
if (path === 'authorization/permissions')
|
|
123
|
+
return { data: { unrestricted: true, permissions: [] } };
|
|
124
|
+
reads += 1;
|
|
125
|
+
if (reads === 2)
|
|
126
|
+
return Response.json(
|
|
127
|
+
{ error: { status: 'INTERNAL', message: 'Unavailable' } },
|
|
128
|
+
{ status: 500 },
|
|
129
|
+
);
|
|
130
|
+
if (reads === 3) await slow;
|
|
131
|
+
return roles;
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
await screen.findByTitle('NocoBase team');
|
|
136
|
+
// Returning from a covering route reads the list again; that read fails.
|
|
137
|
+
await user.click(screen.getByRole('button', { name: 'Cover roles' }));
|
|
138
|
+
await user.click(
|
|
139
|
+
await screen.findByRole('button', { name: 'Back to roles' }),
|
|
140
|
+
);
|
|
141
|
+
await screen.findByText(enUS.listFeedback.refreshFailed);
|
|
142
|
+
vi.useFakeTimers();
|
|
143
|
+
try {
|
|
144
|
+
await act(async () => {
|
|
145
|
+
screen.getByRole('button', { name: enUS.listFeedback.retry }).click();
|
|
146
|
+
});
|
|
147
|
+
await act(() => vi.advanceTimersByTimeAsync(300));
|
|
148
|
+
expect(screen.getAllByRole('status')).toHaveLength(1);
|
|
149
|
+
expect(
|
|
150
|
+
screen.getByRole('button', { name: enUS.listFeedback.retrying }),
|
|
151
|
+
).toBeDisabled();
|
|
152
|
+
expect(screen.getByTitle('NocoBase team')).toBeVisible();
|
|
153
|
+
await act(async () => {
|
|
154
|
+
finish();
|
|
155
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
156
|
+
});
|
|
157
|
+
} finally {
|
|
158
|
+
vi.useRealTimers();
|
|
159
|
+
}
|
|
160
|
+
await waitFor(() =>
|
|
161
|
+
expect(
|
|
162
|
+
screen.queryByText(enUS.listFeedback.refreshFailed),
|
|
163
|
+
).not.toBeInTheDocument(),
|
|
164
|
+
);
|
|
165
|
+
expect(screen.queryByRole('status')).not.toBeInTheDocument();
|
|
166
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Activity, useState, useLayoutEffect } from 'react';
|
|
2
|
+
import { Link, Route, Routes, useLocation } from 'react-router';
|
|
3
|
+
import { renderWithApp } from '@nocobase/app-testing/client';
|
|
4
|
+
import { act, screen, waitFor, within } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
import { expect, it } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
RouteLeaveBoundary,
|
|
9
|
+
usePageLeave,
|
|
10
|
+
} from '../../client/routing/route-leave-boundary.js';
|
|
11
|
+
import enUS from '../../client/locales/en-US.js';
|
|
12
|
+
import metadata from '../../package.json' with { type: 'json' };
|
|
13
|
+
|
|
14
|
+
it('cancels a shell leave confirmation on concealment and requires a fresh decision after recovery', async () => {
|
|
15
|
+
let conceal: (hidden: boolean) => void;
|
|
16
|
+
function Editor() {
|
|
17
|
+
const leave = usePageLeave(true, false);
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<p>Unsaved editor</p>
|
|
21
|
+
{leave.dialog}
|
|
22
|
+
</>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
function Harness() {
|
|
26
|
+
const [hidden, setHidden] = useState(false);
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
conceal = setHidden;
|
|
29
|
+
}, []);
|
|
30
|
+
return (
|
|
31
|
+
<RouteLeaveBoundary hidden={hidden}>
|
|
32
|
+
<Link to='/other'>Leave page</Link>
|
|
33
|
+
<output data-testid='location'>{useLocation().pathname}</output>
|
|
34
|
+
<Routes>
|
|
35
|
+
<Route
|
|
36
|
+
path='/editor'
|
|
37
|
+
element={
|
|
38
|
+
<Activity mode={hidden ? 'hidden' : 'visible'}>
|
|
39
|
+
<Editor />
|
|
40
|
+
</Activity>
|
|
41
|
+
}
|
|
42
|
+
/>
|
|
43
|
+
<Route path='/other' element={<p>Other page</p>} />
|
|
44
|
+
</Routes>
|
|
45
|
+
</RouteLeaveBoundary>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const user = userEvent.setup();
|
|
49
|
+
await renderWithApp(<Harness />, {
|
|
50
|
+
route: '/editor',
|
|
51
|
+
namespace: metadata.name,
|
|
52
|
+
namespaces: { [metadata.name]: enUS },
|
|
53
|
+
});
|
|
54
|
+
await user.click(screen.getByRole('link', { name: 'Leave page' }));
|
|
55
|
+
await screen.findByRole('alertdialog');
|
|
56
|
+
await act(async () => conceal(true));
|
|
57
|
+
expect(screen.queryByRole('alertdialog')).toBeNull();
|
|
58
|
+
await user.click(screen.getByRole('link', { name: 'Leave page' }));
|
|
59
|
+
expect(screen.getByTestId('location')).toHaveTextContent('/editor');
|
|
60
|
+
const unload = new Event('beforeunload', { cancelable: true });
|
|
61
|
+
window.dispatchEvent(unload);
|
|
62
|
+
expect(unload.defaultPrevented).toBe(true);
|
|
63
|
+
await act(async () => conceal(false));
|
|
64
|
+
expect(screen.getByText('Unsaved editor')).toBeVisible();
|
|
65
|
+
expect(screen.getByTestId('location')).toHaveTextContent('/editor');
|
|
66
|
+
expect(screen.queryByRole('alertdialog')).toBeNull();
|
|
67
|
+
await user.click(screen.getByRole('link', { name: 'Leave page' }));
|
|
68
|
+
const question = await screen.findByRole('alertdialog');
|
|
69
|
+
await user.click(within(question).getByRole('button', { name: 'Confirm' }));
|
|
70
|
+
await waitFor(() => expect(screen.getByText('Other page')).toBeVisible());
|
|
71
|
+
const after = new Event('beforeunload', { cancelable: true });
|
|
72
|
+
window.dispatchEvent(after);
|
|
73
|
+
expect(after.defaultPrevented).toBe(false);
|
|
74
|
+
});
|