diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f7b0ccda..668838dc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -5,16 +5,20 @@ on:
branches: [main]
jobs:
+ tests:
+ uses: ./.github/workflows/reusable-test.yml
+
build:
+ needs: [tests]
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
- - run: npm install
+ - run: npm ci
- run: npm run build
diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml
new file mode 100644
index 00000000..7c6a2a26
--- /dev/null
+++ b/.github/workflows/reusable-test.yml
@@ -0,0 +1,55 @@
+name: Reusable Tests
+
+on:
+ workflow_call:
+
+permissions:
+ contents: read
+
+jobs:
+ test-unit:
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout ๐
+ uses: actions/checkout@v6
+ - name: node 22 โ๏ธ
+ uses: actions/setup-node@v6
+ with:
+ node-version: '22'
+ cache: 'npm'
+ - name: install dependencies ๐ฆ
+ run: npm ci
+ - name: lint ๐งน
+ run: npm run lint
+ - name: unit tests ๐งช
+ run: npm run test-coverage
+ - name: build ๐๏ธ
+ run: npm run build
+ - name: Coveralls
+ uses: coverallsapp/github-action@v2
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ test-e2e:
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout ๐
+ uses: actions/checkout@v6
+ - name: node 22 โ๏ธ
+ uses: actions/setup-node@v6
+ with:
+ node-version: '22'
+ cache: 'npm'
+ - name: install dependencies ๐ฆ
+ run: npm ci
+ - name: install Playwright browsers ๐ญ
+ run: npx playwright install --with-deps chromium firefox webkit
+ - name: e2e tests ๐
+ run: npm run test:e2e
+ - name: upload Playwright report ๐
+ if: failure()
+ uses: actions/upload-artifact@v7
+ with:
+ name: playwright-report
+ path: playwright-report/
+ retention-days: 7
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e9c62a9a..426f60f9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,30 +4,10 @@ on:
pull_request:
branches: ['main']
-jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v5
-
- - name: Setup Node
- uses: actions/setup-node@v6
- with:
- node-version: '22'
- cache: 'npm'
-
- - run: npm install
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
- - name: Check Prettier formatting
- run: npx prettier --check vite.config.ts src/
-
- - name: Run ESLint
- run: npx eslint vite.config.ts src/
-
- - run: npm run test-coverage
- - run: npm run build
- - name: Coveralls
- uses: coverallsapp/github-action@master
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
+jobs:
+ tests:
+ uses: ./.github/workflows/reusable-test.yml
diff --git a/package-lock.json b/package-lock.json
index 9b34f48e..348cfc9e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"@eslint/compat": "^2.0.0",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
+ "@playwright/test": "^1.59.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1",
@@ -1059,6 +1060,22 @@
"url": "https://github.com/sponsors/Boshen"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
+ "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.59.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@react-leaflet/core": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz",
@@ -6770,6 +6787,53 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
+ "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.59.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
+ "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
diff --git a/package.json b/package.json
index b20d7eec..6ade3b26 100644
--- a/package.json
+++ b/package.json
@@ -14,12 +14,15 @@
"start": "vite",
"build": "vite build",
"preview": "vite preview",
- "serve": "serve public"
+ "serve": "serve public",
+ "test:e2e": "playwright test",
+ "test:e2e:ui": "playwright test --ui"
},
"devDependencies": {
"@eslint/compat": "^2.0.0",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
+ "@playwright/test": "^1.59.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1",
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 00000000..54f1f289
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig, devices } from '@playwright/test';
+
+export default defineConfig({
+ testDir: './tests/e2e',
+ testMatch: '**/*.spec.ts',
+ fullyParallel: true,
+ forbidOnly: !!process.env.CI,
+ retries: process.env.CI ? 2 : 0,
+ reporter: [['list'], ['html', { open: 'never' }]],
+ use: {
+ baseURL: 'http://localhost:5173',
+ trace: 'on-first-retry',
+ },
+ projects: [
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
+ { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
+ { name: 'webkit', use: { ...devices['Desktop Safari'] } },
+ ],
+ webServer: {
+ command: 'npm run dev',
+ url: 'http://localhost:5173',
+ reuseExistingServer: !process.env.CI,
+ },
+});
diff --git a/tests/e2e/fixture.html b/tests/e2e/fixture.html
new file mode 100644
index 00000000..970a0b8f
--- /dev/null
+++ b/tests/e2e/fixture.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ TSML UI E2E
+
+
+
+
+
+
diff --git a/tests/e2e/tsml.spec.ts b/tests/e2e/tsml.spec.ts
new file mode 100644
index 00000000..996fa2b1
--- /dev/null
+++ b/tests/e2e/tsml.spec.ts
@@ -0,0 +1,95 @@
+import { test, expect, type Page } from '@playwright/test';
+
+const MEETINGS = [
+ {
+ slug: 'monday-morning-group',
+ name: 'Monday Morning Group',
+ day: 1,
+ time: '09:00',
+ timezone: 'America/New_York',
+ formatted_address: '1 Water St, Vineyard Haven, MA 02568, USA',
+ location: 'Steamship Terminal',
+ regions: ['Martha\'s Vineyard'],
+ types: ['O'],
+ },
+ {
+ slug: 'wednesday-evening-group',
+ name: 'Wednesday Evening Group',
+ day: 3,
+ time: '19:00',
+ timezone: 'America/New_York',
+ formatted_address: '188 Meeting St, Charleston, SC 29401, USA',
+ location: 'City Market',
+ regions: ['Charleston'],
+ types: ['C'],
+ },
+ {
+ slug: 'friday-online-group',
+ name: 'Friday Online Group',
+ day: 5,
+ time: '20:00',
+ timezone: 'America/Los_Angeles',
+ conference_url: 'https://zoom.us/j/999000999',
+ formatted_address: 'Online',
+ regions: ['Remote'],
+ types: ['O'],
+ },
+];
+
+async function mockApi(page: Page) {
+ await page.route('http://tsml.e2e.test/**', route =>
+ route.fulfill({ json: MEETINGS })
+ );
+}
+
+async function loadApp(page: Page) {
+ await mockApi(page);
+ await page.goto('/tests/e2e/fixture.html');
+ // Wait until at least one meeting row is visible
+ await expect(
+ page.getByRole('cell', { name: 'Monday Morning Group', exact: true })
+ ).toBeVisible({ timeout: 15000 });
+}
+
+const meetingCell = (page: Page, name: string) => page.getByRole('cell', { name, exact: true });
+
+test.describe('meeting list', () => {
+ test('renders all meetings from the API', async ({ page }) => {
+ await loadApp(page);
+ await expect(meetingCell(page, 'Monday Morning Group')).toBeVisible();
+ await expect(meetingCell(page, 'Wednesday Evening Group')).toBeVisible();
+ await expect(meetingCell(page, 'Friday Online Group')).toBeVisible();
+ });
+
+ test('search filters meetings by name', async ({ page }) => {
+ await loadApp(page);
+ await page.getByRole('searchbox').fill('wednesday');
+ // Wait for exactly 1 name cell, confirms filter fully applied before checking negatives
+ await expect(page.locator('td.tsml-name')).toHaveCount(1);
+ await expect(meetingCell(page, 'Wednesday Evening Group')).toBeVisible();
+ await expect(meetingCell(page, 'Monday Morning Group')).not.toBeVisible();
+ await expect(meetingCell(page, 'Friday Online Group')).not.toBeVisible();
+ });
+});
+
+test.describe('meeting detail', () => {
+ test('clicking a meeting opens the detail view', async ({ page }) => {
+ await loadApp(page);
+ await meetingCell(page, 'Monday Morning Group').click();
+ await expect(
+ page.getByRole('link', { name: 'Back to Meetings' })
+ ).toBeVisible();
+ await expect(page.getByText('Monday Morning Group')).toBeVisible();
+ });
+
+ test('navigating directly to a meeting slug loads the detail view', async ({
+ page,
+ }) => {
+ await mockApi(page);
+ await page.goto('/tests/e2e/fixture.html#/wednesday-evening-group');
+ await expect(
+ page.getByRole('link', { name: 'Back to Meetings' })
+ ).toBeVisible({ timeout: 15000 });
+ await expect(page.getByText('Wednesday Evening Group')).toBeVisible();
+ });
+});
diff --git a/test/__fixtures__/index.ts b/tests/unit/__fixtures__/index.ts
similarity index 100%
rename from test/__fixtures__/index.ts
rename to tests/unit/__fixtures__/index.ts
diff --git a/test/__fixtures__/json.ts b/tests/unit/__fixtures__/json.ts
similarity index 100%
rename from test/__fixtures__/json.ts
rename to tests/unit/__fixtures__/json.ts
diff --git a/test/__fixtures__/meeting.ts b/tests/unit/__fixtures__/meeting.ts
similarity index 71%
rename from test/__fixtures__/meeting.ts
rename to tests/unit/__fixtures__/meeting.ts
index 689e0249..43a2dd6c 100644
--- a/test/__fixtures__/meeting.ts
+++ b/tests/unit/__fixtures__/meeting.ts
@@ -1,4 +1,4 @@
-import type { Meeting } from '../../src/types';
+import type { Meeting } from '../../../src/types';
export const mockMeeting: Meeting = {
slug: 'foo',
diff --git a/test/__mocks__/styleMock.js b/tests/unit/__mocks__/styleMock.js
similarity index 100%
rename from test/__mocks__/styleMock.js
rename to tests/unit/__mocks__/styleMock.js
diff --git a/test/__tests__/Button.spec.tsx b/tests/unit/__tests__/Button.spec.tsx
similarity index 95%
rename from test/__tests__/Button.spec.tsx
rename to tests/unit/__tests__/Button.spec.tsx
index a84db828..f2687837 100644
--- a/test/__tests__/Button.spec.tsx
+++ b/tests/unit/__tests__/Button.spec.tsx
@@ -1,7 +1,7 @@
import { fireEvent, render, screen, cleanup } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
-import Button from '../../src/components/Button';
+import Button from '../../../src/components/Button';
describe('', () => {
afterEach(() => {
diff --git a/test/__tests__/Controls.spec.tsx b/tests/unit/__tests__/Controls.spec.tsx
similarity index 80%
rename from test/__tests__/Controls.spec.tsx
rename to tests/unit/__tests__/Controls.spec.tsx
index 0a226bd4..73ab9e99 100644
--- a/test/__tests__/Controls.spec.tsx
+++ b/tests/unit/__tests__/Controls.spec.tsx
@@ -2,8 +2,8 @@ import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { describe, expect, it } from 'vitest';
-import Controls from '../../src/components/Controls';
-import { defaults, SettingsProvider } from '../../src/hooks';
+import Controls from '../../../src/components/Controls';
+import { defaults, SettingsProvider } from '../../../src/hooks';
describe('', () => {
const settings = defaults;
diff --git a/test/__tests__/Loading.spec.tsx b/tests/unit/__tests__/Loading.spec.tsx
similarity index 83%
rename from test/__tests__/Loading.spec.tsx
rename to tests/unit/__tests__/Loading.spec.tsx
index 9d76affe..7dd270bf 100644
--- a/test/__tests__/Loading.spec.tsx
+++ b/tests/unit/__tests__/Loading.spec.tsx
@@ -1,7 +1,7 @@
import { render } from '@testing-library/react';
import { expect, it } from 'vitest';
-import Loading from '../../src/components/Loading';
+import Loading from '../../../src/components/Loading';
it('', () => {
const { container } = render();
diff --git a/test/__tests__/Map.spec.tsx b/tests/unit/__tests__/Map.spec.tsx
similarity index 97%
rename from test/__tests__/Map.spec.tsx
rename to tests/unit/__tests__/Map.spec.tsx
index 41adf58d..04b5f169 100644
--- a/test/__tests__/Map.spec.tsx
+++ b/tests/unit/__tests__/Map.spec.tsx
@@ -1,7 +1,7 @@
import { render } from '@testing-library/react';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
-import Map from '../../src/components/Map';
+import Map from '../../../src/components/Map';
// Mock react-leaflet
vi.mock('react-leaflet', () => ({
diff --git a/test/__tests__/Meeting.spec.tsx b/tests/unit/__tests__/Meeting.spec.tsx
similarity index 92%
rename from test/__tests__/Meeting.spec.tsx
rename to tests/unit/__tests__/Meeting.spec.tsx
index da0f0a4a..2af75ea0 100644
--- a/test/__tests__/Meeting.spec.tsx
+++ b/tests/unit/__tests__/Meeting.spec.tsx
@@ -3,9 +3,9 @@ import { DateTime } from 'luxon';
import { MemoryRouter, useParams } from 'react-router-dom';
import { beforeEach, describe, expect, it, vi } from 'vitest';
-import Meeting from '../../src/components/Meeting';
-import { SettingsProvider } from '../../src/hooks';
-import { en } from '../../src/i18n';
+import Meeting from '../../../src/components/Meeting';
+import { SettingsProvider } from '../../../src/hooks';
+import { en } from '../../../src/i18n';
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
@@ -57,8 +57,8 @@ const mockMeeting = {
};
// Mock the useData hook to return a predefined meetings object
-vi.mock('../../src/hooks', async () => {
- const originalModule = await vi.importActual('../../src/hooks');
+vi.mock('../../../src/hooks', async () => {
+ const originalModule = await vi.importActual('../../../src/hooks');
return {
...originalModule,
useFilter: () => ({
diff --git a/test/__tests__/Table.spec.tsx b/tests/unit/__tests__/Table.spec.tsx
similarity index 95%
rename from test/__tests__/Table.spec.tsx
rename to tests/unit/__tests__/Table.spec.tsx
index 01e8db31..08869ca3 100644
--- a/test/__tests__/Table.spec.tsx
+++ b/tests/unit/__tests__/Table.spec.tsx
@@ -3,8 +3,8 @@ import { DateTime } from 'luxon';
import { MemoryRouter } from 'react-router-dom';
import { afterEach, describe, expect, it, vi } from 'vitest';
-import Table from '../../src/components/Table';
-import { SettingsProvider } from '../../src/hooks';
+import Table from '../../../src/components/Table';
+import { SettingsProvider } from '../../../src/hooks';
const mockMeetings: Record = {
'meeting-1': {
@@ -41,8 +41,8 @@ const mockFilterState = {
const mockErrorState = { error: null as string | null };
-vi.mock('../../src/hooks', async () => {
- const originalModule = await vi.importActual('../../src/hooks');
+vi.mock('../../../src/hooks', async () => {
+ const originalModule = await vi.importActual('../../../src/hooks');
return {
...originalModule,
useData: () => ({
diff --git a/test/__tests__/analytics.spec.ts b/tests/unit/__tests__/analytics.spec.ts
similarity index 92%
rename from test/__tests__/analytics.spec.ts
rename to tests/unit/__tests__/analytics.spec.ts
index 16cebe1b..72daf519 100644
--- a/test/__tests__/analytics.spec.ts
+++ b/tests/unit/__tests__/analytics.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from 'vitest';
-import { analyticsEvent } from '../../src/helpers/analytics';
+import { analyticsEvent } from '../../../src/helpers/analytics';
window.gtag = vi.fn();
window.ga = vi.fn();
diff --git a/test/__tests__/format-address.spec.ts b/tests/unit/__tests__/format-address.spec.ts
similarity index 86%
rename from test/__tests__/format-address.spec.ts
rename to tests/unit/__tests__/format-address.spec.ts
index 99343da2..d5e1010b 100644
--- a/test/__tests__/format-address.spec.ts
+++ b/tests/unit/__tests__/format-address.spec.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { formatAddress } from '../../src/helpers/format-address';
+import { formatAddress } from '../../../src/helpers/format-address';
describe('formatAddress', () => {
it('returns first part of address if USA address', () => {
diff --git a/test/__tests__/format-array.spec.ts b/tests/unit/__tests__/format-array.spec.ts
similarity index 85%
rename from test/__tests__/format-array.spec.ts
rename to tests/unit/__tests__/format-array.spec.ts
index e3c1051a..3ea40a8d 100644
--- a/test/__tests__/format-array.spec.ts
+++ b/tests/unit/__tests__/format-array.spec.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { formatArray } from '../../src/helpers/format-array';
+import { formatArray } from '../../../src/helpers/format-array';
describe('formatArray', () => {
it.each`
diff --git a/test/__tests__/format-conference-provider.spec.ts b/tests/unit/__tests__/format-conference-provider.spec.ts
similarity index 88%
rename from test/__tests__/format-conference-provider.spec.ts
rename to tests/unit/__tests__/format-conference-provider.spec.ts
index 16e3d9ab..2c151802 100644
--- a/test/__tests__/format-conference-provider.spec.ts
+++ b/tests/unit/__tests__/format-conference-provider.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
-import { formatConferenceProvider } from '../../src/helpers/format-conference-provider';
-import { defaults } from '../../src/hooks';
+import { formatConferenceProvider } from '../../../src/helpers/format-conference-provider';
+import { defaults } from '../../../src/hooks';
describe('formatConferenceProvider', () => {
it.each(['foo', 'https://', 'https://foo.com'])(
diff --git a/test/__tests__/format-directions-url.spec.ts b/tests/unit/__tests__/format-directions-url.spec.ts
similarity index 93%
rename from test/__tests__/format-directions-url.spec.ts
rename to tests/unit/__tests__/format-directions-url.spec.ts
index 42f0098e..9e323e99 100644
--- a/test/__tests__/format-directions-url.spec.ts
+++ b/tests/unit/__tests__/format-directions-url.spec.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { formatDirectionsUrl } from '../../src/helpers/format-directions-url';
+import { formatDirectionsUrl } from '../../../src/helpers/format-directions-url';
describe('formatDirectionsUrl', () => {
const { formatted_address, latitude, longitude } = {
diff --git a/test/__tests__/format-feedback-email.spec.ts b/tests/unit/__tests__/format-feedback-email.spec.ts
similarity index 85%
rename from test/__tests__/format-feedback-email.spec.ts
rename to tests/unit/__tests__/format-feedback-email.spec.ts
index 76401cb3..7de44250 100644
--- a/test/__tests__/format-feedback-email.spec.ts
+++ b/tests/unit/__tests__/format-feedback-email.spec.ts
@@ -1,10 +1,10 @@
import { describe, expect, it, vi } from 'vitest';
-import { formatFeedbackEmail } from '../../src/helpers/format-feedback-email';
-import { defaults } from '../../src/hooks';
-import { Meeting } from '../../src/types';
+import { formatFeedbackEmail } from '../../../src/helpers/format-feedback-email';
+import { defaults } from '../../../src/hooks';
+import { Meeting } from '../../../src/types';
-vi.mock('../../src/helpers/format-url', () => ({
+vi.mock('../../../src/helpers/format-url', () => ({
formatUrl: vi.fn().mockReturnValue('https://foo.com'),
}));
diff --git a/test/__tests__/format-google-url.spec.ts b/tests/unit/__tests__/format-google-url.spec.ts
similarity index 95%
rename from test/__tests__/format-google-url.spec.ts
rename to tests/unit/__tests__/format-google-url.spec.ts
index ed59666e..bf6e9ecd 100644
--- a/test/__tests__/format-google-url.spec.ts
+++ b/tests/unit/__tests__/format-google-url.spec.ts
@@ -1,8 +1,8 @@
import { DateTime } from 'luxon';
import { describe, expect, it } from 'vitest';
-import { formatGoogleUrl } from '../../src/helpers/format-google-url';
-import { Meeting } from '../../src/types';
+import { formatGoogleUrl } from '../../../src/helpers/format-google-url';
+import { Meeting } from '../../../src/types';
const mockMeeting = {
name: 'Foo Meeting',
diff --git a/test/__tests__/format-ics.spec.ts b/tests/unit/__tests__/format-ics.spec.ts
similarity index 95%
rename from test/__tests__/format-ics.spec.ts
rename to tests/unit/__tests__/format-ics.spec.ts
index bf6ef485..ccf07aee 100644
--- a/test/__tests__/format-ics.spec.ts
+++ b/tests/unit/__tests__/format-ics.spec.ts
@@ -1,8 +1,8 @@
import { DateTime } from 'luxon';
import { beforeEach, describe, expect, it, vi } from 'vitest';
-import { formatIcs } from '../../src/helpers/format-ics';
-import { Meeting } from '../../src/types';
+import { formatIcs } from '../../../src/helpers/format-ics';
+import { Meeting } from '../../../src/types';
//TODO: Only requiring the parts needed for this test, should
//probably integrate fixtures.
diff --git a/test/__tests__/format-search.spec.ts b/tests/unit/__tests__/format-search.spec.ts
similarity index 85%
rename from test/__tests__/format-search.spec.ts
rename to tests/unit/__tests__/format-search.spec.ts
index dd779c3c..34e820be 100644
--- a/test/__tests__/format-search.spec.ts
+++ b/tests/unit/__tests__/format-search.spec.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { formatSearch } from '../../src/helpers/format-search';
+import { formatSearch } from '../../../src/helpers/format-search';
describe('formatSearch', () => {
it('should format search strings correctly', () => {
diff --git a/test/__tests__/format-slug.spec.ts b/tests/unit/__tests__/format-slug.spec.ts
similarity index 91%
rename from test/__tests__/format-slug.spec.ts
rename to tests/unit/__tests__/format-slug.spec.ts
index caaf449c..482f04a8 100644
--- a/test/__tests__/format-slug.spec.ts
+++ b/tests/unit/__tests__/format-slug.spec.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { formatSlug } from '../../src/helpers/format-slug';
+import { formatSlug } from '../../../src/helpers/format-slug';
describe('formatSlug', () => {
it('removes accents', () => {
diff --git a/test/__tests__/format-string.spec.ts b/tests/unit/__tests__/format-string.spec.ts
similarity index 82%
rename from test/__tests__/format-string.spec.ts
rename to tests/unit/__tests__/format-string.spec.ts
index aed3d93a..9e295178 100644
--- a/test/__tests__/format-string.spec.ts
+++ b/tests/unit/__tests__/format-string.spec.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { formatString } from '../../src/helpers/format-string';
+import { formatString } from '../../../src/helpers/format-string';
describe('formatString', () => {
it('works with mixed params', () => {
diff --git a/test/__tests__/format-url.spec.ts b/tests/unit/__tests__/format-url.spec.ts
similarity index 88%
rename from test/__tests__/format-url.spec.ts
rename to tests/unit/__tests__/format-url.spec.ts
index c22906c7..1a11e0d6 100644
--- a/test/__tests__/format-url.spec.ts
+++ b/tests/unit/__tests__/format-url.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
-import { formatUrl } from '../../src/helpers/format-url';
-import { defaults } from '../../src/hooks';
+import { formatUrl } from '../../../src/helpers/format-url';
+import { defaults } from '../../../src/hooks';
describe('formatUrl', () => {
it('works with no params', () => {
diff --git a/test/__tests__/get-distance.spec.ts b/tests/unit/__tests__/get-distance.spec.ts
similarity index 92%
rename from test/__tests__/get-distance.spec.ts
rename to tests/unit/__tests__/get-distance.spec.ts
index 42cf5847..ad4b699f 100644
--- a/test/__tests__/get-distance.spec.ts
+++ b/tests/unit/__tests__/get-distance.spec.ts
@@ -1,8 +1,8 @@
import { describe, expect, it } from 'vitest';
-import { getDistance } from '../../src/helpers/get-distance';
-import { defaults as settings } from '../../src/hooks';
+import { getDistance } from '../../../src/helpers/get-distance';
+import { defaults as settings } from '../../../src/hooks';
-import type { Meeting } from '../../src/types';
+import type { Meeting } from '../../../src/types';
describe('distance', () => {
// exact
diff --git a/test/__tests__/get-index-by-key.spec.ts b/tests/unit/__tests__/get-index-by-key.spec.ts
similarity index 94%
rename from test/__tests__/get-index-by-key.spec.ts
rename to tests/unit/__tests__/get-index-by-key.spec.ts
index 28d26671..ea0a91c9 100644
--- a/test/__tests__/get-index-by-key.spec.ts
+++ b/tests/unit/__tests__/get-index-by-key.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
-import { getIndexByKey } from '../../src/helpers/get-index-by-key';
-import type { Index } from '../../src/types';
+import { getIndexByKey } from '../../../src/helpers/get-index-by-key';
+import type { Index } from '../../../src/types';
describe('getIndexByKey', () => {
const mockIndexes: Index[] = [
diff --git a/test/__tests__/inactive-meetings-filter.spec.ts b/tests/unit/__tests__/inactive-meetings-filter.spec.ts
similarity index 96%
rename from test/__tests__/inactive-meetings-filter.spec.ts
rename to tests/unit/__tests__/inactive-meetings-filter.spec.ts
index b1392aac..c719fd7b 100644
--- a/test/__tests__/inactive-meetings-filter.spec.ts
+++ b/tests/unit/__tests__/inactive-meetings-filter.spec.ts
@@ -1,9 +1,9 @@
import { describe, expect, it } from 'vitest';
-import { loadMeetingData } from '../../src/helpers/load-meeting-data';
-import { defaults } from '../../src/hooks';
+import { loadMeetingData } from '../../../src/helpers/load-meeting-data';
+import { defaults } from '../../../src/hooks';
-import type { JSONData } from '../../src/types';
+import type { JSONData } from '../../../src/types';
describe('Inactive meetings in filters', () => {
it('includes inactive meetings in filter indexes when active meetings exist', () => {
diff --git a/test/__tests__/load-meeting-data.spec.ts b/tests/unit/__tests__/load-meeting-data.spec.ts
similarity index 97%
rename from test/__tests__/load-meeting-data.spec.ts
rename to tests/unit/__tests__/load-meeting-data.spec.ts
index e8246b88..0529683c 100644
--- a/test/__tests__/load-meeting-data.spec.ts
+++ b/tests/unit/__tests__/load-meeting-data.spec.ts
@@ -3,10 +3,10 @@ import { describe, expect, it } from 'vitest';
import {
flattenDays,
loadMeetingData,
-} from '../../src/helpers/load-meeting-data';
-import { defaults } from '../../src/hooks';
+} from '../../../src/helpers/load-meeting-data';
+import { defaults } from '../../../src/hooks';
-import type { JSONData } from '../../src/types';
+import type { JSONData } from '../../../src/types';
describe('loadMeetingData', () => {
it('loads data correctly', () => {
diff --git a/test/__tests__/settings.spec.ts b/tests/unit/__tests__/settings.spec.ts
similarity index 97%
rename from test/__tests__/settings.spec.ts
rename to tests/unit/__tests__/settings.spec.ts
index 5beadb74..91e46451 100644
--- a/test/__tests__/settings.spec.ts
+++ b/tests/unit/__tests__/settings.spec.ts
@@ -2,7 +2,7 @@ import { renderHook } from '@testing-library/react';
import { createElement } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
-import { SettingsProvider, useSettings } from '../../src/hooks';
+import { SettingsProvider, useSettings } from '../../../src/hooks';
describe('settings', () => {
let languageGetter: ReturnType;
diff --git a/test/__tests__/streamline-regions-index.spec.ts b/tests/unit/__tests__/streamline-regions-index.spec.ts
similarity index 93%
rename from test/__tests__/streamline-regions-index.spec.ts
rename to tests/unit/__tests__/streamline-regions-index.spec.ts
index 6c886454..567385ba 100644
--- a/test/__tests__/streamline-regions-index.spec.ts
+++ b/tests/unit/__tests__/streamline-regions-index.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
-import { streamlineRegionsIndex } from '../../src/helpers';
-import type { Index } from '../../src/types';
+import { streamlineRegionsIndex } from '../../../src/helpers';
+import type { Index } from '../../../src/types';
describe('streamlineRegionsIndex', () => {
it('removes top level regions that apply to all meetings', () => {
diff --git a/test/__tests__/translate-google-sheet.spec.ts b/tests/unit/__tests__/translate-google-sheet.spec.ts
similarity index 93%
rename from test/__tests__/translate-google-sheet.spec.ts
rename to tests/unit/__tests__/translate-google-sheet.spec.ts
index ecec7690..7939c100 100644
--- a/test/__tests__/translate-google-sheet.spec.ts
+++ b/tests/unit/__tests__/translate-google-sheet.spec.ts
@@ -2,9 +2,9 @@ import { describe, expect, it } from 'vitest';
import {
GoogleSheetData,
translateGoogleSheet,
-} from '../../src/helpers/translate-google-sheet';
-import { defaults } from '../../src/hooks/settings';
-import { JSONData } from '../../src/types';
+} from '../../../src/helpers/translate-google-sheet';
+import { defaults } from '../../../src/hooks/settings';
+import { JSONData } from '../../../src/types';
describe('translateGoogleSheet', () => {
const sheetId = 'abc123';
diff --git a/test/__tests__/user-agent.spec.ts b/tests/unit/__tests__/user-agent.spec.ts
similarity index 93%
rename from test/__tests__/user-agent.spec.ts
rename to tests/unit/__tests__/user-agent.spec.ts
index 2a44a205..b6804a63 100644
--- a/test/__tests__/user-agent.spec.ts
+++ b/tests/unit/__tests__/user-agent.spec.ts
@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-import { iOS } from '../../src/helpers/user-agent';
+import { iOS } from '../../../src/helpers/user-agent';
let platformGetter: ReturnType;
let userAgentGetter: ReturnType;
diff --git a/test/__tests__/validate-input-today.spec.ts b/tests/unit/__tests__/validate-input-today.spec.ts
similarity index 88%
rename from test/__tests__/validate-input-today.spec.ts
rename to tests/unit/__tests__/validate-input-today.spec.ts
index be70bee3..fe19dd19 100644
--- a/test/__tests__/validate-input-today.spec.ts
+++ b/tests/unit/__tests__/validate-input-today.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
-import { validateInput } from '../../src/helpers/validate-input';
-import { defaults } from '../../src/hooks/settings';
+import { validateInput } from '../../../src/helpers/validate-input';
+import { defaults } from '../../../src/hooks/settings';
describe('validateInput with weekday=today', () => {
it('should convert "today" to the current weekday name', () => {
diff --git a/test/setup.ts b/tests/unit/setup.ts
similarity index 100%
rename from test/setup.ts
rename to tests/unit/setup.ts
diff --git a/vite.config.ts b/vite.config.ts
index f5b518ba..2b8e55ba 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -33,8 +33,9 @@ export default defineConfig({
},
},
test: {
+ include: ['tests/unit/**/*.spec.{ts,tsx}'],
environment: 'jsdom',
- setupFiles: ['./test/setup.ts'],
+ setupFiles: ['./tests/unit/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov', 'html'],