diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..1ac029b --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,147 @@ +name: Run tests + +permissions: + contents: read + +on: + workflow_call: + pull_request: + branches: + - main + - '!dependabot/**' + paths: + - src/** + - tests/** + - '!docs/**' + - package.json + - package-lock.json + - svelte.config.* + - vite.config.* + - playwright.config.* + - tsconfig*.json + - .github/workflows/run-tests.yml + +concurrency: + group: run-tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + unit-tests: + runs-on: blacksmith-2vcpu-ubuntu-2404 # trunk-ignore(actionlint/runner-label) + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v6 # zizmor: ignore[unpinned-uses] + with: + persist-credentials: false + fetch-depth: 1 + + - uses: pnpm/action-setup@v4 # zizmor: ignore[unpinned-uses] + with: + version: 10 + + - name: Use Node.js - 24 + uses: actions/setup-node@v6 # zizmor: ignore[unpinned-uses] + with: + node-version: 24 + + - name: Cache build artifacts + uses: actions/cache@v5 # zizmor: ignore[unpinned-uses] + with: + path: | + .svelte-kit + dist + key: ${{ runner.os }}-build-${{ hashFiles('src/**/*', 'pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-build- + + - name: Cache vitest artifacts + uses: actions/cache@v5 # zizmor: ignore[unpinned-uses] + with: + path: | + node_modules/.vitest + test-results + key: ${{ runner.os }}-vitest-${{ hashFiles('pnpm-lock.yaml', 'src/**/*.{test,spec}.{js,ts}', 'tests/**/*.{test,spec}.{js,ts}', 'vitest.config.ts') }} + restore-keys: | + ${{ runner.os }}-vitest- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run unit tests + run: | + pnpm build + pnpm test + + - name: Upload unit test results + if: always() + uses: actions/upload-artifact@v4 # zizmor: ignore[unpinned-uses] + with: + name: unit-test-results + path: | + coverage/** + test-results/** + if-no-files-found: ignore + retention-days: 7 + + e2e-tests: + needs: unit-tests + runs-on: blacksmith-2vcpu-ubuntu-2404 # trunk-ignore(actionlint/runner-label) + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + include: + - shard: 1/2 + shard_name: 1-2 + - shard: 2/2 + shard_name: 2-2 + steps: + - name: Checkout + uses: actions/checkout@v6 # zizmor: ignore[unpinned-uses] + with: + persist-credentials: false + fetch-depth: 1 + + - uses: pnpm/action-setup@v4 # zizmor: ignore[unpinned-uses] + with: + version: 10 + + - name: Use Node.js - 24 + uses: actions/setup-node@v6 # zizmor: ignore[unpinned-uses] + with: + node-version: 24 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Cache Playwright browsers + uses: actions/cache@v5 # zizmor: ignore[unpinned-uses] + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-playwright- + + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps + + - name: Sanity check - list discovered tests + run: pnpm exec playwright test --list | head -n 50 | cat + + - name: Run e2e tests (sharded) + env: + CI: true + run: pnpm exec playwright test --config=playwright.config.ts --shard=${{ matrix.shard }} --reporter=line + + - name: Upload Playwright artifacts + if: always() + uses: actions/upload-artifact@v4 # zizmor: ignore[unpinned-uses] + with: + name: playwright-artifacts-${{ matrix.shard_name }} + path: | + playwright-report/** + test-results/** + blob-report/** + if-no-files-found: ignore + retention-days: 7 diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 8de022b..0717a7e 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -24,6 +24,9 @@ lint: - linters: [markdownlint] paths: - .changeset/** + - linters: [eslint] + paths: + - '**/*.svelte.ts' definitions: - name: prettier files: diff --git a/tests/default.test.ts b/tests/default.test.ts index 94fd200..661c0ad 100644 --- a/tests/default.test.ts +++ b/tests/default.test.ts @@ -6,16 +6,16 @@ test.describe('SvelteDiffMatchPatch', () => { }) test('renders a visible diff', async ({ page }) => { - await page.fill('[data-testid="text1"]', 'hello world') - await page.fill('[data-testid="text2"]', 'hello brave world') + await page.getByTestId('text1').fill('hello world') + await page.getByTestId('text2').fill('hello brave world') await expect(page.getByTestId('diff-result')).toContainText('brave') await expect(page.getByTestId('diff-result')).toContainText('hello') await expect(page.getByTestId('diff-result')).toContainText('world') }) test('applies custom rendererClasses', async ({ page }) => { - await page.fill('[data-testid="text1"]', 'foo shoo') - await page.fill('[data-testid="text2"]', 'bar shoo') + await page.getByTestId('text1').fill('foo shoo') + await page.getByTestId('text2').fill('bar shoo') await expect(page.getByTestId('diff-result').locator('.diff-remove')).toBeVisible() await expect(page.getByTestId('diff-result').locator('.diff-insert')).toBeVisible() await expect(page.getByTestId('diff-result').locator('.diff-equal')).toBeVisible() diff --git a/tests/expected-patterns.test.ts b/tests/expected-patterns.test.ts index cc25f6f..83629eb 100644 --- a/tests/expected-patterns.test.ts +++ b/tests/expected-patterns.test.ts @@ -17,15 +17,10 @@ test.describe('Expected Patterns', () => { }) test('non-captured deviations still show as insert/remove', async ({ page }) => { - // Use a simple template where we can have both expected captures and deviations - await page.getByTestId('text1').fill('Hello (?.+) end') - await page.getByTestId('text2').fill('Goodbye World end') - // "World" is captured as expected, but "Hello" vs "Goodbye" is a real deviation - // Actually regex won't match because "Hello" != "Goodbye" literal... - // Use: template matches but has surrounding diffs from diff algorithm - await page.getByTestId('text1').fill('(?.+) likes coding') + // Use \w+ instead of .+ so the capture is constrained to "Jason" only, + // leaving "coding" vs "hacking" as a real deviation for insert/remove + await page.getByTestId('text1').fill('(?\\w+) likes coding') await page.getByTestId('text2').fill('Jason likes hacking') - // "Jason" is captured, "coding" vs "hacking" is a real deviation await expect( page.getByTestId('diff-result').locator('.diff-expected').first() ).toBeVisible() diff --git a/tests/snippets.test.ts b/tests/snippets.test.ts index e515eee..8c1f320 100644 --- a/tests/snippets.test.ts +++ b/tests/snippets.test.ts @@ -16,8 +16,8 @@ test.describe('SvelteDiffMatchPatch', () => { }) test('applies custom rendererClasses', async ({ page }) => { - await page.fill('[data-testid="text1"]', 'foo shoo') - await page.fill('[data-testid="text2"]', 'bar shoo') + await page.getByTestId('text1').fill('foo shoo') + await page.getByTestId('text2').fill('bar shoo') await expect(page.getByTestId('diff-result').locator('.diff-remove')).toBeVisible() await expect(page.getByTestId('diff-result').locator('.diff-insert')).toBeVisible() await expect(page.getByTestId('diff-result').locator('.diff-equal')).toBeVisible()