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
43 changes: 43 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Integration
'on':
workflow_dispatch: {}
jobs:
integration:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-24.04
- macos-26
- windows-2025
runs-on: ${{ matrix.os }}
timeout-minutes: 60
env:
PLAYWRIGHT_BROWSERS_PATH: 0
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- run: npm ci
- run: npm run build
- uses: actions/checkout@v7
with:
repository: lvce-editor/lvce-editor
ref: 236de223cab8f9eeb72dc68a260b63f0db3b4ef9
path: .tmp/lvce-integration
- uses: actions/setup-node@v6
with:
node-version-file: .tmp/lvce-integration/.nvmrc
- name: Install application dependencies
working-directory: .tmp/lvce-integration
run: npm ci
- name: Install Chromium
working-directory: .tmp/lvce-integration/packages/extension-host-worker-tests
run: npx playwright install --with-deps chromium
- name: Prepare owned integration tests
run: node packages/e2e-integration/prepare.mjs .tmp/lvce-integration
- name: Run owned integration tests
working-directory: .tmp/lvce-integration/packages/extension-host-worker-tests
shell: bash
run: npm run e2e:headless --
17 changes: 16 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,25 @@ export default defineConfig([
},
},
{
files: ['packages/e2e/**/*.ts'],
files: ['packages/{e2e,e2e-integration}/**/*.ts'],
rules: {
'@cspell/spellchecker': 'off',
'e2e/prefer-filesystem-set-files': 'off',
},
},
{
// The pinned application supplies its own Node runtime.
files: ['.github/workflows/integration.yml'],
rules: { 'github-actions/node-version-file': 'off', 'github-actions/on': 'off' },
},
{
// The application runner supplies mutable API objects to these scenarios.
files: ['packages/e2e-integration/src/**/*.ts'],
rules: { '@typescript-eslint/prefer-readonly-parameter-types': 'off' },
},
{
// The application runner supplies mutable API objects to these scenarios.
files: ['packages/e2e-integration/src/**/*.ts'],
rules: { '@typescript-eslint/prefer-readonly-parameter-types': 'off' },
},
])
15 changes: 15 additions & 0 deletions packages/e2e-integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Application integration tests

These scenarios and fixtures moved from `lvce-editor` to `lvce-editor/diff-view`. The Integration workflow overlays this repository's build in a pinned, disposable LVCE checkout and runs the application's existing test runner.

The workflow preserves the original CI commands, settings, and platform restrictions. Scenarios that were outside the application's CI selection remain available for local runs; their existing skip declarations are unchanged. Repositories with no previously selected CI scenarios expose a manual Integration workflow.

Build this repository, install the pinned application's dependencies and Chromium, then run:

```sh
node packages/e2e-integration/prepare.mjs /path/to/disposable/lvce-editor
cd /path/to/disposable/lvce-editor/packages/extension-host-worker-tests
npm run e2e:headless --
```

Preparation replaces the disposable application's scenarios and fixtures and overlays local build artifacts. See `config.json` for artifact and script destinations, and `.github/workflows/integration.yml` for static export, Electron, and settings requirements. Update the pinned application commit when its runtime needs updating.
4 changes: 4 additions & 0 deletions packages/e2e-integration/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"artifacts": [[".tmp/dist", "packages/renderer-worker/node_modules/@lvce-editor/diff-view"]],
"scripts": []
}
35 changes: 35 additions & 0 deletions packages/e2e-integration/prepare.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { cp, mkdir, readdir, readFile, realpath, rm } from 'node:fs/promises'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

const here = dirname(fileURLToPath(import.meta.url))
const owner = resolve(here, '../..')
if (!process.argv[2]) throw new Error('Pass the path to a disposable LVCE checkout')
const application = resolve(process.argv[2])
const manifest = JSON.parse(await readFile(join(application, 'package.json'), 'utf8'))
if (manifest.name !== 'lvce-editor') throw new Error('Expected an LVCE application checkout')
const config = JSON.parse(await readFile(join(here, 'config.json'), 'utf8'))
const tests = join(application, 'packages/extension-host-worker-tests')
// Keep the application's runner and replace only its test inventory.
for (const name of await readdir(join(tests, 'src'))) {
if (name !== '_all.js') await rm(join(tests, 'src', name), { recursive: true })
}
await cp(join(here, 'src'), join(tests, 'src'), { recursive: true })
await rm(join(tests, 'fixtures'), { recursive: true, force: true })
await mkdir(join(tests, 'fixtures'), { recursive: true })
try {
await cp(join(here, 'fixtures'), join(tests, 'fixtures'), { recursive: true })
} catch (error) {
if (error.code !== 'ENOENT') throw error
}
for (const path of config.scripts) {
await cp(join(here, 'scripts', path.split('/').at(-1)), join(application, path))
}
// Exercise this repository's build in the pinned application runtime.
for (const [from, to] of config.artifacts) {
const target = await realpath(join(application, to)).catch((error) => {
if (error.code !== 'ENOENT') throw error
return join(application, to)
})
await cp(join(owner, from), target, { recursive: true })
}
20 changes: 20 additions & 0 deletions packages/e2e-integration/src/viewlet.diff-editor-deletion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Test } from '@lvce-editor/test-with-playwright'

export const name = 'sample.diff-editor-deletion'

export const test: Test = async ({ expect, FileSystem, Locator, Main, Workspace }) => {
// arrange
const tmpDir = await FileSystem.getTmpDir()
await FileSystem.writeFile(`${tmpDir}/file-1.txt`, `abc`)
await FileSystem.writeFile(`${tmpDir}/file-2.txt`, ``)
await Workspace.setUri(tmpDir)

// act
await Main.openUri(`diff://${tmpDir}/file-1.txt<->${tmpDir}/file-2.txt`)

// assert
const contentLeft = Locator('.DiffEditorContentLeft .DiffEditorRows')
const contentRight = Locator('.DiffEditorContentRight .DiffEditorRows')
await expect(contentLeft).toHaveText('abc')
await expect(contentRight).toHaveText('')
}
16 changes: 16 additions & 0 deletions packages/e2e-integration/src/viewlet.diff-editor-inline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Test } from '@lvce-editor/test-with-playwright'

export const name = 'viewlet.diff-editor-inline'

export const test: Test = async ({ expect, FileSystem, Locator, Main, Workspace }) => {
const tmpDir = await FileSystem.getTmpDir()
const filePath = `${tmpDir}/file.ts`
await FileSystem.writeFile(filePath, 'const value = 2')
await Workspace.setUri(tmpDir)

await Main.openUri(`inline-diff://data://const value = 1<->${filePath}`)

const diffEditor = Locator('.Viewlet.DiffEditor')
await expect(diffEditor).toBeVisible()
await expect(Locator('.MainTab')).toHaveText('file.ts (Working Tree)')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Test } from '@lvce-editor/test-with-playwright'

export const name = 'sample.diff-editor-insertion-at-start-and-end'

export const test: Test = async ({ expect, FileSystem, Locator, Main, Workspace }) => {
// arrange
const tmpDir = await FileSystem.getTmpDir()
await FileSystem.writeFile(`${tmpDir}/file-1.txt`, `c`)
await FileSystem.writeFile(
`${tmpDir}/file-2.txt`,
`a
b
c
d`,
)
await Workspace.setUri(tmpDir)

// act
await Main.openUri(`diff://${tmpDir}/file-1.txt<->${tmpDir}/file-2.txt`)

// assert
const contentLeft = Locator('.DiffEditorContentLeft .DiffEditorRows')
const contentRight = Locator('.DiffEditorContentRight .DiffEditorRows')
await expect(contentLeft).toHaveText('c')
await expect(contentRight).toHaveText('abcd')
}
24 changes: 24 additions & 0 deletions packages/e2e-integration/src/viewlet.diff-editor-insertion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Test } from '@lvce-editor/test-with-playwright'

export const name = 'sample.diff-editor-insertion'

export const test: Test = async ({ expect, FileSystem, Locator, Main, Workspace }) => {
// arrange
const tmpDir = await FileSystem.getTmpDir()
await FileSystem.writeFile(`${tmpDir}/file-1.txt`, ``)
await FileSystem.writeFile(`${tmpDir}/file-2.txt`, `def`)
await Workspace.setUri(tmpDir)

// act
await Main.openUri(`diff://${tmpDir}/file-1.txt<->${tmpDir}/file-2.txt`)

// assert
const contentLeft = Locator('.DiffEditorContentLeft .DiffEditorRows')
const contentRight = Locator('.DiffEditorContentRight .DiffEditorRows')
await expect(contentLeft).toHaveText('')
await expect(contentRight).toHaveText('def')
const rowLeft = contentLeft.locator('.EditorRow')
await expect(rowLeft).toHaveClass('Deletion')
const rowRight = contentRight.locator('.EditorRow')
await expect(rowRight).toHaveClass('Insertion')
}
38 changes: 38 additions & 0 deletions packages/e2e-integration/src/viewlet.diff-editor-scrolling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { Test } from '@lvce-editor/test-with-playwright'

const getContent = (bottomLine: string): string => {
const lines: string[] = []
for (let index = 1; index <= 1800; index++) {
lines.push(`shared line ${index}`)
}
lines.push(bottomLine)
return lines.join('\n')
}

export const name = 'viewlet.diff-editor-scrolling'

export const test: Test = async ({ expect, FileSystem, Locator, Main, Workspace }) => {
const tmpDir = await FileSystem.getTmpDir()
await FileSystem.writeFile(`${tmpDir}/file-1.txt`, getContent('bottom change before'))
await FileSystem.writeFile(`${tmpDir}/file-2.txt`, getContent('bottom change after'))
await Workspace.setUri(tmpDir)

await Main.openUri(`diff://${tmpDir}/file-1.txt<->${tmpDir}/file-2.txt`)

const line1 = Locator('.DiffEditorContentLeft .DiffEditorLineNumber', {
hasText: '1',
})
await expect(line1).toBeVisible()

const contentRight = Locator('.DiffEditorContentRight')
await contentRight.dispatchEvent('wheel', {
bubbles: true,
deltaMode: 0,
deltaY: 9_999_999,
})

const line1800 = Locator('.DiffEditorContentLeft .DiffEditorLineNumber', {
hasText: '1800',
})
await expect(line1800).toBeVisible()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Test } from '@lvce-editor/test-with-playwright'

export const name = 'viewlet.diff-editor-syntax-highlighting'

export const test: Test = async ({ expect, FileSystem, Locator, Main, Workspace }) => {
const tmpDir = await FileSystem.getTmpDir()
const filePath = `${tmpDir}/file.ts`
await FileSystem.writeFile(filePath, 'const rightValue = 2')
await Workspace.setUri(tmpDir)

await Main.openUri(`diff://data://const leftValue = 1<->${filePath}`)

await expect(Locator('.DiffEditorContentLeft .Token.Keyword')).toHaveText('const')
await expect(Locator('.DiffEditorContentLeft .Token.Numeric')).toHaveText('1')
await expect(Locator('.DiffEditorContentRight .Token.Keyword')).toHaveText('const')
await expect(Locator('.DiffEditorContentRight .Token.Numeric')).toHaveText('2')
}
20 changes: 20 additions & 0 deletions packages/e2e-integration/src/viewlet.diff-editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Test } from '@lvce-editor/test-with-playwright'

export const name = 'sample.diff-editor'

export const test: Test = async ({ expect, FileSystem, Locator, Main, Workspace }) => {
// arrange
const tmpDir = await FileSystem.getTmpDir()
await FileSystem.writeFile(`${tmpDir}/file-1.txt`, `abc`)
await FileSystem.writeFile(`${tmpDir}/file-2.txt`, `def`)
await Workspace.setUri(tmpDir)

// act
await Main.openUri(`diff://${tmpDir}/file-1.txt<->${tmpDir}/file-2.txt`)

// assert
const contentLeft = Locator('.DiffEditorContentLeft .DiffEditorRows')
const contentRight = Locator('.DiffEditorContentRight .DiffEditorRows')
await expect(contentLeft).toHaveText('abc')
await expect(contentRight).toHaveText('def')
}
20 changes: 20 additions & 0 deletions packages/e2e-integration/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["esnext", "WebWorker"],
"module": "nodenext",
"moduleResolution": "nodenext",
"skipLibCheck": false,
"noEmit": true,
"strict": true,
"noImplicitAny": true,
"allowJs": true,
"types": [],
"allowImportingTsExtensions": true,
"assumeChangesOnlyAffectDirectDependencies": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"noUncheckedSideEffectImports": true
},
"include": ["src", "fixtures"]
}
Loading