diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-02-15 15:17:03 -0500 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-02-21 17:49:38 -0500 |
| commit | fc0f1654a56d2247728a766f43e72ff169704888 (patch) | |
| tree | 945f44c9a90bf51de20c61a5a8c5ed82c2c05009 /ui/tests/lib/components | |
| parent | 36cadfe00cacc6a6523f9862d3f7a08a9d0ce611 (diff) | |
Hoist global state access out of individual components.
Access to "global" (maybe "external?") state is now handled at the top level of the component hierarchy, in `+page.svelte`, `+layout.svelte`, and their associated scripts. State is otherwise passed down through props, and changes are passed up through callbacks.
This is - hopefully - groundwork for refactoring state management a bit. I wanted to move access to state out to a smaller number of places, so that I have fewer places to update to implement reconnect logic. My broader goal is to make it easier to refactor these kinds of external side effects, as well, though no such changes are in this branch.
This change also makes testing a mile easier, since tests can interact with props and callbacks instead of emulating the whole HTTP request stack and the Pilcrow API. This change removes do-very-little tests.
Diffstat (limited to 'ui/tests/lib/components')
| -rw-r--r-- | ui/tests/lib/components/ActiveChannel.svelte.test.js | 22 | ||||
| -rw-r--r-- | ui/tests/lib/components/ChangePassword.svelte.test.js | 90 | ||||
| -rw-r--r-- | ui/tests/lib/components/Channel.svelte.test.js | 22 | ||||
| -rw-r--r-- | ui/tests/lib/components/ChannelList.svelte.test.js | 22 | ||||
| -rw-r--r-- | ui/tests/lib/components/CreateChannelForm.svelte.test.js | 65 | ||||
| -rw-r--r-- | ui/tests/lib/components/LogIn.svelte.test.js | 38 | ||||
| -rw-r--r-- | ui/tests/lib/components/MessageInput.svelte.test.js | 61 | ||||
| -rw-r--r-- | ui/tests/lib/components/MessageRun.svelte.test.js | 22 |
8 files changed, 127 insertions, 215 deletions
diff --git a/ui/tests/lib/components/ActiveChannel.svelte.test.js b/ui/tests/lib/components/ActiveChannel.svelte.test.js deleted file mode 100644 index 183c823..0000000 --- a/ui/tests/lib/components/ActiveChannel.svelte.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { flushSync, mount, unmount } from 'svelte'; -import { afterEach, beforeEach, describe, expect, test } from 'vitest'; -import ActiveChannel from '$lib/components/ActiveChannel.svelte'; - -let component; - -describe('ActiveChannel', () => { - beforeEach(() => { - component = mount(ActiveChannel, { - target: document.body // `document` exists because of jsdom - }); - flushSync(); - }); - - afterEach(() => { - unmount(component); - }); - - test('mounts', async () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/ui/tests/lib/components/ChangePassword.svelte.test.js b/ui/tests/lib/components/ChangePassword.svelte.test.js index 9eb40f5..9db6974 100644 --- a/ui/tests/lib/components/ChangePassword.svelte.test.js +++ b/ui/tests/lib/components/ChangePassword.svelte.test.js @@ -1,52 +1,66 @@ -import { flushSync, mount, unmount } from 'svelte'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import userEvent from '@testing-library/user-event'; +import { beforeEach, expect, test, describe, it, vi } from 'vitest'; import ChangePassword from '$lib/components/ChangePassword.svelte'; -import axios from 'axios'; -let component; +const user = userEvent.setup(); -let mocks = vi.hoisted(() => ({ - post: vi.fn() +const mocks = vi.hoisted(() => ({ + changePassword: vi.fn() })); -describe('ChangePassword', () => { - beforeEach(() => { - vi.mock('axios', async (importActual) => { - const actual = await importActual(); - - const mockAxios = { - default: { - ...actual.default, - create: vi.fn(() => ({ - ...actual.default.create(), - post: mocks.post - })) - } - }; - - return mockAxios; +describe('ChangePassword', async () => { + beforeEach(async () => { + render(ChangePassword, { + changePassword: mocks.changePassword }); - mocks.post.mockResolvedValue({ status: 200 }); + }); - component = mount(ChangePassword, { - target: document.body // `document` exists because of jsdom - }); - flushSync(); + it('submits valid password changes', async () => { + const oldPassword = screen.getByLabelText('current password'); + await user.type(oldPassword, 'old password'); + + const newPassword = screen.getByLabelText('new password'); + await user.type(newPassword, 'new password'); + + const confirmPassword = screen.getByLabelText('confirm new password'); + await user.type(confirmPassword, 'new password'); + + const create = screen.getByRole('button'); + await user.click(create); + + expect(mocks.changePassword).toHaveBeenCalledExactlyOnceWith('old password', 'new password'); }); - afterEach(() => { - unmount(component); + it('is disabled when old password matches new password', async () => { + const oldPassword = screen.getByLabelText('new password'); + await user.type(oldPassword, 'old password'); + + const newPassword = screen.getByLabelText('new password'); + await user.type(newPassword, 'new password'); + + const confirmPassword = screen.getByLabelText('confirm new password'); + await user.type(confirmPassword, 'new password'); + + const create = screen.getByRole('button'); + await user.click(create); + + expect(mocks.changePassword).not.toHaveBeenCalled(); }); - test('onsubmit happy path', async () => { - // Set value in all three inputs at once: - const inputs = document.body.querySelectorAll('input[type=password]'); - inputs.value = 'pass'; - // Click the button, then flush the changes so you can synchronously write expectations - document.body.querySelector('button[type=submit]').click(); - flushSync(); + it('is disabled when new passwords differ', async () => { + const oldPassword = screen.getByLabelText('new password'); + await user.type(oldPassword, 'old password'); + + const newPassword = screen.getByLabelText('new password'); + await user.type(newPassword, 'new password A'); + + const confirmPassword = screen.getByLabelText('confirm new password'); + await user.type(confirmPassword, 'new password B'); + + const create = screen.getByRole('button'); + await user.click(create); - // expect(axios.post).toHaveBeenCalledWith('/password', { password: 'pass', to: 'pass' }); - expect(Array.from(inputs.values()).map((i) => i.value)).toEqual(['', '', '']); + expect(mocks.changePassword).not.toHaveBeenCalled(); }); }); diff --git a/ui/tests/lib/components/Channel.svelte.test.js b/ui/tests/lib/components/Channel.svelte.test.js deleted file mode 100644 index a6fdab9..0000000 --- a/ui/tests/lib/components/Channel.svelte.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { flushSync, mount, unmount } from 'svelte'; -import { afterEach, beforeEach, describe, expect, test } from 'vitest'; -import Channel from '$lib/components/Channel.svelte'; - -let component; - -describe('Channel', () => { - beforeEach(() => { - component = mount(Channel, { - target: document.body // `document` exists because of jsdom - }); - flushSync(); - }); - - afterEach(() => { - unmount(component); - }); - - test('mounts', async () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/ui/tests/lib/components/ChannelList.svelte.test.js b/ui/tests/lib/components/ChannelList.svelte.test.js deleted file mode 100644 index 7b5ae4a..0000000 --- a/ui/tests/lib/components/ChannelList.svelte.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { flushSync, mount, unmount } from 'svelte'; -import { afterEach, beforeEach, describe, expect, test } from 'vitest'; -import ChannelList from '$lib/components/ChannelList.svelte'; - -let component; - -describe('ChannelList', () => { - beforeEach(() => { - component = mount(ChannelList, { - target: document.body // `document` exists because of jsdom - }); - flushSync(); - }); - - afterEach(() => { - unmount(component); - }); - - test('mounts', async () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/ui/tests/lib/components/CreateChannelForm.svelte.test.js b/ui/tests/lib/components/CreateChannelForm.svelte.test.js index a4a4695..15d3cfd 100644 --- a/ui/tests/lib/components/CreateChannelForm.svelte.test.js +++ b/ui/tests/lib/components/CreateChannelForm.svelte.test.js @@ -1,52 +1,37 @@ -import { flushSync, mount, unmount } from 'svelte'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import userEvent from '@testing-library/user-event'; +import { beforeEach, expect, test, describe, it, vi } from 'vitest'; import CreateChannelForm from '$lib/components/CreateChannelForm.svelte'; -import axios from 'axios'; -let component; +const user = userEvent.setup(); -let mocks = vi.hoisted(() => ({ - post: vi.fn() +const mocks = vi.hoisted(() => ({ + createChannel: vi.fn() })); -describe('CreateChannelForm', () => { - beforeEach(() => { - vi.mock('axios', async (importActual) => { - const actual = await importActual(); - - const mockAxios = { - default: { - ...actual.default, - create: vi.fn(() => ({ - ...actual.default.create(), - post: mocks.post - })) - } - }; - - return mockAxios; +describe('CreateChannelForm', async () => { + beforeEach(async () => { + render(CreateChannelForm, { + createChannel: mocks.createChannel }); - mocks.post.mockResolvedValue({ status: 200 }); - - component = mount(CreateChannelForm, { - target: document.body // `document` exists because of jsdom - }); - flushSync(); }); - afterEach(() => { - unmount(component); - }); + describe('creates channels', async () => { + it('with a non-empty name', async () => { + const input = screen.getByRole('textbox'); + await user.type(input, 'channel name'); - test('onsubmit happy path', async () => { - // Set value on the one input this should match: - const inputs = document.body.querySelectorAll('input[type=text]'); - inputs.value = 'channel name'; - // Click the button, then flush the changes so you can synchronously write expectations - document.body.querySelector('button[type=submit]').click(); - flushSync(); + const create = screen.getByRole('button'); + await user.click(create); - expect(mocks.post).toHaveBeenCalled(); - expect(Array.from(inputs.values()).map((i) => i.value)).toEqual(['']); + expect(mocks.createChannel).toHaveBeenCalledExactlyOnceWith('channel name'); + }); + + it('with an empty name', async () => { + const create = screen.getByRole('button'); + await user.click(create); + + expect(mocks.createChannel).toHaveBeenCalledExactlyOnceWith(''); + }); }); }); diff --git a/ui/tests/lib/components/LogIn.svelte.test.js b/ui/tests/lib/components/LogIn.svelte.test.js index b64d846..ab77c11 100644 --- a/ui/tests/lib/components/LogIn.svelte.test.js +++ b/ui/tests/lib/components/LogIn.svelte.test.js @@ -1,22 +1,34 @@ -import { flushSync, mount, unmount } from 'svelte'; -import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import userEvent from '@testing-library/user-event'; +import { beforeEach, expect, test, describe, it, vi } from 'vitest'; import LogIn from '$lib/components/LogIn.svelte'; -let component; +const user = userEvent.setup(); -describe('LogIn', () => { - beforeEach(() => { - component = mount(LogIn, { - target: document.body // `document` exists because of jsdom +const mocks = vi.hoisted(() => ({ + logIn: vi.fn() +})); + +describe('LogIn', async () => { + beforeEach(async () => { + render(LogIn, { + logIn: mocks.logIn }); - flushSync(); }); - afterEach(() => { - unmount(component); - }); + it('sends a login request', async () => { + const username = screen.getByLabelText('username'); + await user.type(username, 'my username'); + + const password = screen.getByLabelText('password'); + await user.type(password, 'my very creative and long password'); + + const signIn = screen.getByRole('button'); + await user.click(signIn); - test('mounts', async () => { - expect(component).toBeTruthy(); + expect(mocks.logIn).toHaveBeenCalledExactlyOnceWith( + 'my username', + 'my very creative and long password' + ); }); }); diff --git a/ui/tests/lib/components/MessageInput.svelte.test.js b/ui/tests/lib/components/MessageInput.svelte.test.js index 3dde5d7..508fb43 100644 --- a/ui/tests/lib/components/MessageInput.svelte.test.js +++ b/ui/tests/lib/components/MessageInput.svelte.test.js @@ -1,48 +1,37 @@ -import { flushSync, mount, unmount } from 'svelte'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import userEvent from '@testing-library/user-event'; +import { beforeEach, expect, test, describe, it, vi } from 'vitest'; import MessageInput from '$lib/components/MessageInput.svelte'; -import axios from 'axios'; -let component; +const user = userEvent.setup(); -let mocks = vi.hoisted(() => ({ - post: vi.fn() +const mocks = vi.hoisted(() => ({ + sendMessage: vi.fn() })); -describe('MessageInput', () => { - beforeEach(() => { - vi.mock('axios', async (importActual) => { - const actual = await importActual(); - - const mockAxios = { - default: { - ...actual.default, - create: vi.fn(() => ({ - ...actual.default.create(), - post: mocks.post - })) - } - }; - - return mockAxios; +describe('CreateChannelForm', async () => { + beforeEach(async () => { + render(MessageInput, { + sendMessage: mocks.sendMessage }); - mocks.post.mockResolvedValue({ status: 200 }); - - component = mount(MessageInput, { - target: document.body // `document` exists because of jsdom - }); - flushSync(); }); - afterEach(() => { - unmount(component); - }); + describe('sends a message', async () => { + it('with non-empty content', async () => { + const input = screen.getByRole('textbox'); + await user.type(input, 'a happy surprise'); - test('onsubmit happy path', async () => { - // Click the button, then flush the changes so you can synchronously write expectations - document.body.querySelector('button[type=submit]').click(); - flushSync(); + const send = screen.getByRole('button'); + await user.click(send); - expect(mocks.post).toHaveBeenCalled(); + expect(mocks.sendMessage).toHaveBeenCalledExactlyOnceWith('a happy surprise'); + }); + + it('with empty content', async () => { + const send = screen.getByRole('button'); + await user.click(send); + + expect(mocks.sendMessage).toHaveBeenCalledExactlyOnceWith(''); + }); }); }); diff --git a/ui/tests/lib/components/MessageRun.svelte.test.js b/ui/tests/lib/components/MessageRun.svelte.test.js deleted file mode 100644 index 7671c52..0000000 --- a/ui/tests/lib/components/MessageRun.svelte.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { flushSync, mount, unmount } from 'svelte'; -import { afterEach, beforeEach, describe, expect, test } from 'vitest'; -import MessageRun from '$lib/components/MessageRun.svelte'; - -let component; - -describe('MessageRun', () => { - beforeEach(() => { - component = mount(MessageRun, { - target: document.body // `document` exists because of jsdom - }); - flushSync(); - }); - - afterEach(() => { - unmount(component); - }); - - test('mounts', async () => { - expect(component).toBeTruthy(); - }); -}); |
