Rework app bar, notes layout, Settings/Admin/login restyle
Build and Push Docker Image / build-and-push (push) Failing after 20s
Build and Push Docker Image / build-and-push (push) Failing after 20s
App bar: brand, Studies / Read Bible / Reading Plans, and one account menu (theme, Settings, Admin, Log out). Study stage: notes get their own box above the resources in the right column. Settings, Admin and the login page use the reader themes; add a theme-aware danger colour. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
31abee0695
commit
ecb1c701fa
22 files changed
+351
-289
No files matched your search
+74
-5
@@ -76,6 +76,12 @@ async function renderApp() {
|
||||
await waitFor(() => expect(screen.queryByText('Loading…')).not.toBeInTheDocument());
|
||||
}
|
||||
|
||||
// The account menu (avatar button in the app bar) holds the email, Settings, Admin and Log out.
|
||||
async function openAccountMenu(user = userEvent.setup()) {
|
||||
await user.click(await screen.findByRole('button', { name: /account menu/i }));
|
||||
return user;
|
||||
}
|
||||
|
||||
async function loadChapter() {
|
||||
const user = userEvent.setup();
|
||||
await renderApp();
|
||||
@@ -412,6 +418,7 @@ describe('Authentication', () => {
|
||||
const u = userEvent.setup();
|
||||
render(<App />);
|
||||
await signInWithForm(u);
|
||||
await openAccountMenu(u);
|
||||
expect(await screen.findByText(user.email)).toBeInTheDocument();
|
||||
expect(calledWith(fetchMock, 'POST', '/api/auth/login')).toBe(true);
|
||||
await waitFor(() => expect(calledWith(fetchMock, 'GET', '/api/reader/ink')).toBe(true));
|
||||
@@ -424,6 +431,7 @@ describe('Authentication', () => {
|
||||
await signInWithForm(u);
|
||||
await u.type(await screen.findByPlaceholderText('123456'), '123456');
|
||||
await u.click(screen.getByRole('button', { name: /verify/i }));
|
||||
await openAccountMenu(u);
|
||||
expect(await screen.findByText(user.email)).toBeInTheDocument();
|
||||
expect(calledWith(fetchMock, 'POST', '/api/auth/mfa/verify')).toBe(true);
|
||||
});
|
||||
@@ -432,8 +440,9 @@ describe('Authentication', () => {
|
||||
const fetchMock = stubApi({ session: user });
|
||||
const u = userEvent.setup();
|
||||
render(<App />);
|
||||
await openAccountMenu(u);
|
||||
expect(await screen.findByText(user.email)).toBeInTheDocument();
|
||||
await u.click(screen.getByRole('button', { name: /log out/i }));
|
||||
await u.click(screen.getByRole('menuitem', { name: /log out/i }));
|
||||
expect(await screen.findByRole('button', { name: /^sign in$/i })).toBeInTheDocument();
|
||||
expect(calledWith(fetchMock, 'POST', '/api/auth/logout')).toBe(true);
|
||||
});
|
||||
@@ -442,7 +451,8 @@ describe('Authentication', () => {
|
||||
stubApi({ session: user });
|
||||
const u = userEvent.setup();
|
||||
render(<App />);
|
||||
await u.click(await screen.findByRole('button', { name: /settings/i }));
|
||||
await openAccountMenu(u);
|
||||
await u.click(await screen.findByRole('menuitem', { name: /settings/i }));
|
||||
expect(await screen.findByDisplayValue('My Show')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -479,7 +489,8 @@ describe('Admin page', () => {
|
||||
stubAdminApi(admin);
|
||||
const u = userEvent.setup();
|
||||
render(<App />);
|
||||
await u.click(await screen.findByRole('button', { name: /admin/i }));
|
||||
await openAccountMenu(u);
|
||||
await u.click(await screen.findByRole('menuitem', { name: /admin/i }));
|
||||
expect(await screen.findByText('student@example.com')).toBeInTheDocument();
|
||||
await u.click(screen.getByRole('button', { name: /projects \(1\)/i }));
|
||||
expect(await screen.findByText('Romans Study')).toBeInTheDocument();
|
||||
@@ -490,7 +501,8 @@ describe('Admin page', () => {
|
||||
window.confirm.mockReturnValue(true);
|
||||
const u = userEvent.setup();
|
||||
render(<App />);
|
||||
await u.click(await screen.findByRole('button', { name: /admin/i }));
|
||||
await openAccountMenu(u);
|
||||
await u.click(await screen.findByRole('menuitem', { name: /admin/i }));
|
||||
const row = (await screen.findByText('student@example.com')).closest('tr');
|
||||
await u.click(within(row).getByRole('button', { name: /delete/i }));
|
||||
await waitFor(() => expect(screen.queryByText('student@example.com')).not.toBeInTheDocument());
|
||||
@@ -500,8 +512,10 @@ describe('Admin page', () => {
|
||||
test('non-admins get no Admin button', async () => {
|
||||
stubAdminApi({ ...admin, isAdmin: false });
|
||||
render(<App />);
|
||||
await openAccountMenu();
|
||||
expect(await screen.findByText(admin.email)).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /admin/i })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('menuitem', { name: /settings/i })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('menuitem', { name: /admin/i })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -881,3 +895,58 @@ describe('Study workspace', () => {
|
||||
expect(screen.getByText(/\d+ words · about/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// App bar and study layout
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('App bar', () => {
|
||||
test('shows the three destinations, and the account menu switches the theme', async () => {
|
||||
const user = userEvent.setup();
|
||||
await renderApp();
|
||||
expect(screen.getByRole('button', { name: /^studies$/i })).toHaveAttribute('aria-current', 'page');
|
||||
expect(screen.getByRole('button', { name: /read bible/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /reading plans/i })).toBeInTheDocument();
|
||||
await openAccountMenu(user);
|
||||
await user.click(screen.getByRole('button', { name: /^dark$/i }));
|
||||
expect(localStorage.getItem('reader-theme')).toBe('dark');
|
||||
// Local-only mode has no signed-in user, so there is no Settings/Log out to show
|
||||
expect(screen.queryByRole('menuitem', { name: /log out/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('the account menu closes on Escape', async () => {
|
||||
const user = userEvent.setup();
|
||||
await renderApp();
|
||||
await openAccountMenu(user);
|
||||
expect(screen.getByRole('button', { name: /^paper$/i })).toBeInTheDocument();
|
||||
await user.keyboard('{Escape}');
|
||||
expect(screen.queryByRole('button', { name: /^paper$/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('the brand button and Studies return Home from another page', async () => {
|
||||
const user = userEvent.setup();
|
||||
await renderApp();
|
||||
await user.click(screen.getByRole('button', { name: /import session list/i }));
|
||||
await screen.findByRole('heading', { name: /import session list/i });
|
||||
await user.click(screen.getByRole('button', { name: /bible study home/i }));
|
||||
expect(await screen.findByText('My Studies')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Study layout', () => {
|
||||
test('notes sit in their own box above the resources, not under the passage', async () => {
|
||||
const u = await loadChapter();
|
||||
addChunk('Paul, a servant', 'In hope of eternal');
|
||||
await screen.findByText('1 created');
|
||||
await u.click(screen.getByRole('button', { name: /begin studying/i }));
|
||||
await findChunkCounter(1, 1);
|
||||
const notes = screen.getByRole('region', { name: 'Notes' });
|
||||
const resources = screen.getByRole('region', { name: 'Resources' });
|
||||
expect(within(notes).getByRole('textbox', { name: 'Observe' })).toBeInTheDocument();
|
||||
expect(within(resources).getByRole('tab', { name: /commentary/i })).toBeInTheDocument();
|
||||
// notes come first in the right column
|
||||
expect(notes.compareDocumentPosition(resources) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
// and the passage column no longer holds them
|
||||
expect(within(screen.getByRole('region', { name: 'Scripture' })).queryByRole('textbox')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user