Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/validate-branch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ jobs:
use-private-registry: false

e2e:
name: e2e (nuxt ${{ matrix.nuxt }})
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
# The module supports both majors, so the suite runs against both.
# The lockfile pins Nuxt 4; the Nuxt 3 leg downgrades on top of it.
nuxt:
- '3'
- '4'
steps:
- uses: actions/checkout@v4

Expand All @@ -31,6 +40,12 @@ jobs:

- run: npm ci

# @nuxt/kit is a direct dependency ranged across both majors, so it has
# to be pinned alongside nuxt; otherwise the module keeps loading the
# newest kit while the app runs the other major.
- name: Install Nuxt ${{ matrix.nuxt }}
run: npm install --no-save nuxt@^${{ matrix.nuxt }} @nuxt/kit@^${{ matrix.nuxt }}

- run: npm run build

- name: Get Playwright version
Expand Down
2 changes: 1 addition & 1 deletion e2e/app/.nuxtrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
setups.@nuxt/test-utils="3.23.0"
setups.@nuxt/test-utils="4.3.2"
3 changes: 3 additions & 0 deletions e2e/specs/ssr/identity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {test, expect} from '@playwright/test';
import type {Page} from '@playwright/test';
import {Token} from '@croct/sdk/token';
import {DEFAULT_CREDENTIALS} from '../../constants';
import {waitForHydration} from '../../utils';

type Identity = {
clientId: string,
Expand Down Expand Up @@ -62,6 +63,8 @@ test.describe('identity', () => {

const {clientId, userToken} = await getIdentity(page);

await waitForHydration(page);

await page.getByRole('link', {name: 'identity'}).click();

await expect(page.getByTestId('server-evaluation-token')).toHaveText(userToken);
Expand Down
3 changes: 3 additions & 0 deletions e2e/specs/ssr/page-context.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {test, expect} from '@playwright/test';
import {APP_PORT} from '../../constants';
import {waitForHydration} from '../../utils';

const APP_URL = `http://localhost:${APP_PORT}`;

Expand All @@ -25,6 +26,8 @@ test.describe('page context', () => {
test('should report the page navigated to on the client', async ({page}) => {
await page.goto('/');

await waitForHydration(page);

await page.getByRole('link', {name: 'page context'}).click();

await expect(page.getByTestId('evaluation-url')).toHaveText(PAGE_URL);
Expand Down
21 changes: 21 additions & 0 deletions e2e/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type {Page} from '@playwright/test';

// Nuxt exposes useNuxtApp on the window in the browser, but only Nuxt 4 ships
// the ambient declaration for it, so it is typed locally to keep the helper
// working across both major versions.
type NuxtWindow = {
useNuxtApp?: () => {isHydrating: boolean},
};

/**
* Waits until the Nuxt app has hydrated.
*
* NuxtLink only intercepts clicks once the app is hydrated. Clicking earlier
* performs a full document navigation, so the page is rendered on the server
* and the specs end up asserting the server context instead of the browser one.
*/
export function waitForHydration(page: Page): Promise<unknown> {
return page.waitForFunction(
() => (window as unknown as NuxtWindow).useNuxtApp?.().isHydrating === false,
);
}
13 changes: 13 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import {defineConfig} from 'eslint/config';
import {configs} from '@croct/eslint-plugin';
import {createTypeScriptImportResolver} from 'eslint-import-resolver-typescript';

export default defineConfig(
configs.typescript,
{
settings: {
/*
Without an explicit resolver, import-x falls back to the legacy "node" one, which is
not installed. Resolution then silently yields nothing, and no-cycle walking into
node_modules loads whatever sits at <package>/node as the resolver, which crashes the
run on packages shipping native bindings there, such as lightningcss.
This resolver also understands the tsconfig paths for #app and #imports.
*/
'import-x/resolver-next': [createTypeScriptImportResolver({project: './tsconfig.json'})],
},
},
{
rules: {
'func-names': 'off',
Expand Down
Loading
Loading