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
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,37 @@ jobs:
- name: Check formatting of everything (prettier)
run: |
npm run fmt:check

e2e-test:
name: E2E Tests (Playwright)
timeout-minutes: 60
# run only if triggered by push on a branch or by a PR event for a PR which is not a draft
if: ${{ !github.event.pull_request || github.event.pull_request.draft == false }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6

- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version-file: ".nvmrc"
cache: npm

- name: Install dependencies
run: |
npm ci

- name: Install Playwright browsers
run: |
npx playwright install --with-deps

- name: Run Playwright tests
run: |
npm run test:e2e

- uses: actions/upload-artifact@v6
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ npm-debug.log*

# Automatically generated files by GitHub Actions workflow
/.shared-workflows

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ To install and set up the project, follow these steps:
1. Ensure you have Node.js v20 installed. You can download it from the [official Node.js website](https://nodejs.org/).
2. Clone the repository to your local machine.
3. Install the project dependencies using npm - `npm install`.
4. Install Playwright browsers if you plan to run the E2E tests: `npx playwright install --with-deps`.

This will install all the necessary packages and dependencies required to run the project.

Expand All @@ -28,6 +29,7 @@ Open [http://localhost:5173](http://localhost:5173) with your browser to see the

- `npm run start`: Starts the development server.
- `npm run build`: Builds the app for production.
- `npm run test:e2e`: Runs all End-to-End tests with Playwright.

## Configuration

Expand Down
58 changes: 58 additions & 0 deletions e2e-tests/code-editing-and-ast-interaction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Tests for code editing functionality and AST tool interaction.
*/
import test, { expect } from "@playwright/test";

/**
* This test verifies that:
* - Users can edit code in the editor
* - The AST updates in response to code changes
* - ESQuery selectors correctly highlight matching code and AST nodes
* - AST node expansion functionality works properly
*/
test(`should change code, then highlight code and AST nodes matching ESQuery selector`, async ({
page,
}) => {
await page.goto("/");

// focus code editor textbox
await page
.getByRole("region", { name: "Code Editor Panel" })
.getByRole("textbox")
.nth(1)
.click();

// delete the default code
await page.keyboard.press("ControlOrMeta+KeyA");
await page.keyboard.press("Backspace");

// add new code
await page.keyboard.type("console.log('Hello, World!');");

// add an ESQuery selector
await page.getByRole("textbox", { name: "ESQuery Selector" }).click();
await page.keyboard.type("CallExpression");

// wait for the debounced update of the AST to happen
await expect(
page
.getByRole("listitem")
.filter({ hasText: "end" })
.filter({ hasText: "29" }),
).toBeVisible();

// expand AST nodes for ExpressionStatement and CallExpression
await page
.getByRole("region", { name: "Program" })
.getByRole("listitem")
.filter({ hasText: "bodyArray[1 element]" })
.getByLabel("Toggle Property")
.click();
await page.getByRole("button", { name: "ExpressionStatement" }).click();
await page
.getByRole("region", { name: "Program" })
.getByRole("listitem")
.filter({ hasText: "expressionCallExpression{type" })
.getByLabel("Toggle Property")
.click();
});
20 changes: 20 additions & 0 deletions e2e-tests/light-dark-theme.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Tests for theme switching functionality.
*/

import { test } from "@playwright/test";

/**
* This test verifies that:
* - The application shows light theme by default
* - Users can toggle between light and dark themes
* - Theme changes are visually reflected in the UI
*/
test("should show light theme by default and switch to dark theme", async ({
page,
}) => {
await page.goto("/");

await page.getByRole("button", { name: "Toggle theme" }).click();
await page.getByRole("menuitem", { name: "Dark" }).click();
});
28 changes: 28 additions & 0 deletions e2e-tests/options.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Tests for language selection and options panel functionality.
*/
import { test } from "@playwright/test";

/**
* This test verifies that:
* - Users can open the language options popover
* - Users can switch between supported languages (JavaScript, JSON, Markdown, CSS, HTML)
* - For each language the entire page is correctly rendered
*/
test("should switch language and show options for each", async ({ page }) => {
await page.goto("/");

await page.getByRole("button", { name: "Language Options" }).click();

await page.getByRole("combobox", { name: "Language" }).click();
await page.getByRole("option", { name: "JSON JSON" }).click();

await page.getByRole("combobox", { name: "Language" }).click();
await page.getByRole("option", { name: "Markdown Markdown" }).click();

await page.getByRole("combobox", { name: "Language" }).click();
await page.getByRole("option", { name: "CSS CSS" }).click();

await page.getByRole("combobox", { name: "Language" }).click();
await page.getByRole("option", { name: "HTML HTML" }).click();
});
21 changes: 21 additions & 0 deletions e2e-tests/tools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Tests for the Code Analysis Tools Panel.
*/
import { test } from "@playwright/test";

/**
* This test verifies that:
* - Users can switch between different code analysis tools (AST, Scope, Code Path)
* - Each tool displays correctly
* - Tool-specific interactions work as expected (e.g. scope selection)
*/
test("should switch to each tool and show it", async ({ page }) => {
await page.goto("/");

await page.getByRole("button", { name: "Scope" }).click();
await page.getByRole("button", { name: "global" }).click();
// move mouse away to avoid accordion hover state
await page.mouse.move(0, 0);

await page.getByRole("button", { name: "Code Path" }).click();
});
64 changes: 64 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"lint": "eslint",
"lint:fix": "eslint --fix",
"fmt": "prettier --write .",
"fmt:check": "prettier --check ."
"fmt:check": "prettier --check .",
"test:e2e": "playwright test"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
Expand Down Expand Up @@ -72,6 +73,7 @@
},
"devDependencies": {
"@eslint/core": "^0.17.0",
"@playwright/test": "^1.57.0",
"@types/eslint-scope": "^8.3.2",
"@types/espree": "^10.1.0",
"@types/esquery": "^1.5.4",
Expand Down
60 changes: 60 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import os from "node:os";

import { defineConfig, devices } from "@playwright/test";

const countOfCpus = os.cpus().length;
const workers = countOfCpus
? // utilize all logical processors up to a max of 4 to limit RAM usage
Math.min(countOfCpus, 4)
: undefined;

const isInCi = process.env.CI === "true";

export default defineConfig({
testDir: "./e2e-tests",
fullyParallel: true,
// fail a Playwright run in CI if some test.only is in the source code
forbidOnly: isInCi,
retries: isInCi ? 1 : 0,
workers,
reporter: isInCi ? [["html"], ["github"]] : "html",

use: {
baseURL: `http://localhost:5173`,

// ensure consistent timezone and locale
timezoneId: "America/Los_Angeles",
locale: "en-US",

// always capture trace and video (seems to not have significant performance impact)
trace: "on",
video: "on",
},

projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
// opt into "New Headless" chromium (https://playwright.dev/docs/browsers#chromium-new-headless-mode, https://developer.chrome.com/docs/chromium/headless)
channel: "chromium",
},
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
],

webServer: [
{
command: "npm run start",
url: "http://localhost:5173",
reuseExistingServer: !isInCi,
},
],
});
14 changes: 12 additions & 2 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ function App() {
direction="horizontal"
className="border-t h-full"
>
<Panel defaultSize={50} minSize={25}>
<Panel
defaultSize={50}
minSize={25}
role="region"
aria-label="Code Editor Panel"
>
<EsquerySelectorInput />
<Editor
value={code[language]}
Expand All @@ -47,7 +52,12 @@ function App() {
/>
</Panel>
<PanelResizeHandle className="w-2 bg-gutter dark:bg-gray-600 bg-gray-200 bg-no-repeat bg-center" />
<Panel defaultSize={50} minSize={25}>
<Panel
defaultSize={50}
minSize={25}
role="region"
aria-label="Code Analysis Tools Panel"
>
<div className="bg-muted overflow-auto h-full relative flex flex-col">
<div className="flex sm:items-center flex-col sm:flex-row justify-between p-4 gap-2 z-10">
<ToolSelector />
Expand Down
Loading
Loading