Skip to content
Open
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
44 changes: 22 additions & 22 deletions apps/editor/e2e/git.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,24 +77,24 @@ test('git: VCS menu, commit panel, log, branches, remote, conflict, amend, stash
await showPanel(page, 'Commit');
await expect(commitPanel(page).getByText('This project is not a git repository')).toBeVisible();
await vcs(page, 'Initialize repository');
await expect(group(page, 'Unversioned Files')).toBeVisible();
await expect(group(page, 'Unversioned files')).toBeVisible();
git(PONG, 'config', 'user.name', 'E2E');
git(PONG, 'config', 'user.email', 'e2e@example.com');

const nested = row(page, 'Unversioned Files', 'apps/client/src/main.ts');
const nested = row(page, 'Unversioned files', 'apps/client/src/main.ts');
await expect(nested).toContainText('main.ts');
await expect(nested).toContainText('apps/client/src');
await message(page).fill('first commit');
await expect(commitButton).toBeDisabled();
const includeAll = group(page, 'Unversioned Files').getByRole('checkbox', {
name: 'Include all: Unversioned Files',
const includeAll = group(page, 'Unversioned files').getByRole('checkbox', {
name: 'Include all: Unversioned files',
includeHidden: true,
});
await nested.getByRole('checkbox').check();
expect(await includeAll.evaluate((box: HTMLInputElement) => box.indeterminate)).toBe(true);
await includeAll.check();
await commit(page, 'first commit');
await expect(row(page, 'Unversioned Files', 'package.json')).toHaveCount(0);
await expect(row(page, 'Unversioned files', 'package.json')).toHaveCount(0);
await expect(branchStatus(page, 'main')).toBeVisible();

await showPanel(page, 'Git');
Expand Down Expand Up @@ -181,23 +181,23 @@ test('git: VCS menu, commit panel, log, branches, remote, conflict, amend, stash
await page.keyboard.press('Space');
await expect(changed.getByRole('checkbox')).toBeChecked();
await changed.click({ button: 'right' });
await expect(page.getByRole('menuitem', { name: /^Show Diff/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /^Jump to Source/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /^Show diff/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /^Jump to source/ })).toBeVisible();
await page.getByRole('menuitem', { name: /^Rollback…/ }).click();
await expect(page.getByRole('heading', { name: `Rollback ${FILE}?` })).toBeVisible();
await page.getByRole('button', { name: 'Rollback', exact: true }).last().click();
await expect(changed).toHaveCount(0);
writeFileSync(join(PONG, NEW_FILE), 'new\n');
await row(page, 'Unversioned Files', NEW_FILE).click({ button: 'right' });
await row(page, 'Unversioned files', NEW_FILE).click({ button: 'right' });
await page.getByRole('menuitem', { name: 'Add to VCS' }).click();
await expect(row(page, 'Changes', NEW_FILE)).toBeVisible();
await row(page, 'Changes', NEW_FILE).click();
await rollBack.click();
await page.getByRole('button', { name: 'Rollback', exact: true }).last().click();
await row(page, 'Unversioned Files', NEW_FILE).click();
await row(page, 'Unversioned files', NEW_FILE).click();
await page.keyboard.press('Delete');
await page.getByRole('button', { name: 'Delete', exact: true }).last().click();
await expect(row(page, 'Unversioned Files', NEW_FILE)).toHaveCount(0);
await expect(row(page, 'Unversioned files', NEW_FILE)).toHaveCount(0);

await vcs(page, 'New branch…');
await page.getByRole('textbox', { name: 'Branch name' }).fill('feature');
Expand All @@ -207,7 +207,7 @@ test('git: VCS menu, commit panel, log, branches, remote, conflict, amend, stash
await local.getByRole('button', { name: 'main', exact: true }).dblclick();
await expect(branchStatus(page, 'main')).toBeVisible();
await local.getByRole('button', { name: 'feature', exact: true }).click({ button: 'right' });
await expect(page.getByRole('menuitem', { name: "New Branch from 'feature'…" })).toBeVisible();
await expect(page.getByRole('menuitem', { name: "New branch from 'feature'…" })).toBeVisible();
await page.getByRole('menuitem', { name: 'Delete', exact: true }).click();
await page.getByRole('button', { name: 'Delete', exact: true }).last().click();
await expect(local.getByRole('button', { name: 'feature', exact: true })).toHaveCount(0);
Expand All @@ -229,7 +229,7 @@ test('git: VCS menu, commit panel, log, branches, remote, conflict, amend, stash
writeFileSync(join(OTHER, FILE), 'theirs\n');
git(OTHER, 'commit', '--quiet', '-am', 'their change');
git(OTHER, 'push', '--quiet');
await logPanel(page).getByRole('button', { name: 'Fetch All Remotes' }).click();
await logPanel(page).getByRole('button', { name: 'Fetch all remotes' }).click();
await expect(branchStatus(page, 'main ↓1')).toBeVisible();
const remote = logPanel(page).getByRole('list', { name: 'Branches of origin' });
await remote.getByRole('button', { name: 'main', exact: true }).click();
Expand All @@ -248,12 +248,12 @@ test('git: VCS menu, commit panel, log, branches, remote, conflict, amend, stash
await expect(branchStatus(page, 'main ↑1 ↓1')).toBeVisible();
await vcs(page, 'Update project');
await expect(page.getByText('The update stopped on conflicts').last()).toBeVisible();
const conflict = row(page, 'Merge Conflicts', FILE);
const conflict = row(page, 'Merge conflicts', FILE);
await expect(conflict).toBeVisible();
writeFileSync(join(PONG, FILE), 'both\n');
await conflict.getByRole('button', { name: 'Mark resolved' }).click();
await expect(group(page, 'Merge Conflicts')).toHaveCount(0);
await commit(page, 'merge', 'Commit and Push…');
await expect(group(page, 'Merge conflicts')).toHaveCount(0);
await commit(page, 'merge', 'Commit and push…');
const afterMerge = await push();
await expect(afterMerge.getByRole('list', { name: 'Commits to push' })).toContainText('merge');
await afterMerge.getByRole('button', { name: 'Push', exact: true }).click();
Expand All @@ -267,7 +267,7 @@ test('git: VCS menu, commit panel, log, branches, remote, conflict, amend, stash
await commit(page, 'merge, amended');
await expect(commits(page).getByRole('row').first()).toContainText('merge, amended');
await expect(changed).toHaveCount(0);
await commitPanel(page).getByRole('button', { name: 'Commit Message History' }).click();
await commitPanel(page).getByRole('button', { name: 'Commit message history' }).click();
await page.getByRole('menuitem', { name: 'my change' }).click();
await expect(message(page)).toHaveValue('my change');
await message(page).fill('');
Expand Down Expand Up @@ -299,9 +299,9 @@ test('browses, folds and includes changes from the keyboard', async ({ page }) =
await showPanel(page, 'Commit');
await vcs(page, 'Initialize repository');
const files = commitPanel(page).getByRole('group', { name: 'Changed files' });
const node = commitPanel(page).getByRole('button', { name: /^Unversioned Files, / });
const node = commitPanel(page).getByRole('button', { name: /^Unversioned files, / });
const includeAll = commitPanel(page).getByRole('checkbox', {
name: 'Include all: Unversioned Files',
name: 'Include all: Unversioned files',
includeHidden: true,
});
const active = () =>
Expand All @@ -314,7 +314,7 @@ test('browses, folds and includes changes from the keyboard', async ({ page }) =
await page.keyboard.press('ArrowDown');
await expect.poll(active).toMatch(/^Changes, 0 files/);
await page.keyboard.press('ArrowDown');
await expect.poll(active).toMatch(/^Unversioned Files, /);
await expect.poll(active).toMatch(/^Unversioned files, /);
await page.keyboard.press('Space');
await expect(includeAll).toBeChecked();
await page.keyboard.press('Space');
Expand All @@ -324,12 +324,12 @@ test('browses, folds and includes changes from the keyboard', async ({ page }) =
await page.keyboard.press('ArrowRight');
await expect(node).toHaveAttribute('aria-expanded', 'true');
await page.keyboard.press('ArrowRight');
await expect.poll(active).not.toMatch(/^Unversioned Files/);
await expect.poll(active).not.toMatch(/^Unversioned files/);
await expect(
group(page, 'Unversioned Files').getByRole('option', { selected: true }),
group(page, 'Unversioned files').getByRole('option', { selected: true }),
).toHaveCount(1);
await page.keyboard.press('ArrowLeft');
await expect.poll(active).toMatch(/^Unversioned Files, /);
await expect.poll(active).toMatch(/^Unversioned files, /);
await resetLayout(page);
/** The layout is saved after 500 ms. */
await page.waitForTimeout(900);
Expand Down
15 changes: 12 additions & 3 deletions apps/editor/e2e/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { type Page, expect } from '@playwright/test';

/** Puts the saved layout of the project back to the default (tests reuse projects). */
/**
* Puts the saved layout of the project back to the default with every panel (tests reuse
* projects, and expect the panels of the full layout).
*/
export const resetLayout = async (page: Page): Promise<void> => {
await page.getByRole('button', { name: 'View', exact: true }).click();
await page.getByRole('menuitem', { name: 'Layouts' }).click();
await page.getByRole('menuitem', { name: 'Reset layout' }).click();
await expect(page.getByText('Layout reset to default').last()).toBeVisible();
await page.getByRole('menuitem', { name: 'All panels' }).click();
await expect(page.getByText('Layout reset with all panels').last()).toBeVisible();
};

/** Expands folders of the Files tree, from the root (already expanded ones stay open). */
Expand All @@ -32,6 +35,12 @@ export const panelTab = (page: Page, name: string | RegExp) =>
*/
export const showPanel = async (page: Page, name: string | RegExp): Promise<void> => {
const tab = panelTab(page, name);
/** A panel the layout does not show (History, by default) opens from View › Panels. */
if (typeof name === 'string' && !(await tab.count())) {
await page.getByRole('button', { name: 'View', exact: true }).click();
await page.getByRole('menuitem', { name: 'Panels' }).click();
await page.getByRole('menuitem', { name, exact: true }).click();
}
await expect(tab).toBeVisible();
if ((await tab.getAttribute('aria-selected')) !== 'true') await tab.click();
await expect(tab).toHaveAttribute('aria-selected', 'true');
Expand Down
5 changes: 4 additions & 1 deletion apps/editor/e2e/history-panel.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { existsSync } from 'node:fs';
import { join } from 'node:path';

import { openProject } from './helpers';
import { openProject, resetLayout, showPanel } from './helpers';
import { type Page, expect, test } from './test';
import { PONG } from './workspace';

Expand All @@ -14,6 +14,9 @@ const row = (page: Page, name: string) => files(page).getByRole('treeitem', { na
const open = async (page: Page) => {
await openProject(page, 'pong');
await page.waitForURL(/\/project\//);
await resetLayout(page);
/** History is not shown by default. */
await showPanel(page, 'History');
await expect(row(page, 'apps')).toBeVisible();
};

Expand Down
10 changes: 6 additions & 4 deletions apps/editor/e2e/inspectors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,16 @@ test.describe('with the engine', () => {

/** The count shown; screen readers get it only once a search has settled. */
const count = page.getByText(/of \d+ packets/).first();
/**
* While hidden (still mounted), the panel records nothing: read its count twice while it is
* hidden, once the packets already on their way have landed.
*/
await showPanel(page, 'Console');
await page.waitForTimeout(1500);
await showPanel(page, 'Network');
await page.waitForTimeout(500);
const before = await count.textContent();
await showPanel(page, 'Console');
await page.waitForTimeout(2500);
await showPanel(page, 'Network');
expect(await count.textContent()).toBe(before);
await showPanel(page, 'Network');
await expect(page.getByRole('img', { name: 'Bytes per second' })).toBeVisible({
timeout: 10_000,
});
Expand Down
39 changes: 38 additions & 1 deletion apps/editor/e2e/workbench.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expandFolders, openProject, panelTab, resetLayout } from './helpers';
import { expandFolders, openProject, panelTab, resetLayout, showPanel } from './helpers';
import { type Locator, type Page, expect, test } from './test';

const openPong = async (page: Page) => {
Expand Down Expand Up @@ -55,6 +55,7 @@ test('moves, resizes and floats docks, undoes, and restores the layout after rel
}) => {
await openPong(page);

await showPanel(page, 'History');
await drag(page, panelTab(page, 'History'), panelTab(page, 'Console'));
await expect(stackOf(page, 'Console')).toHaveAttribute('data-slot', 'bottom');
await expect(page.locator('[data-slot="bottom"][data-nf-drop="stack"]').first()).toHaveAttribute(
Expand Down Expand Up @@ -196,3 +197,39 @@ test('the project name in the status bar shows the Project screen, it no longer
);
await expect(page).toHaveURL(/\/project\//);
});

test('a simple layout shows the main panels only, all panels come back from the View menu', async ({
page,
}) => {
await openPong(page);
const layouts = async (entry: string) => {
await page.getByRole('button', { name: 'View', exact: true }).click();
await page.getByRole('menuitem', { name: 'Layouts' }).click();
await page.getByRole('menuitem', { name: entry }).click();
};
const stripes = page.getByRole('navigation', { name: /tool windows$/ });
try {
await layouts('Simple layout');
await expect(page.getByText('Layout reset with the main panels').last()).toBeVisible();
for (const name of ['Files', 'Hierarchy', 'Inspector', 'Console'])
await expect(stripes.getByRole('tab', { name, exact: true })).toBeVisible();
for (const name of ['Commit', 'Profiler', 'History', 'Components'])
await expect(stripes.getByRole('tab', { name, exact: true })).toHaveCount(0);
await layouts('All panels');
await expect(stripes.getByRole('tab', { name: 'Commit', exact: true })).toBeVisible();
} finally {
await resetLayout(page);
await page.waitForTimeout(900);
}
});

test('says what each screen is for', async ({ page }) => {
await openPong(page);
const screens = page.getByRole('tablist', { name: 'Main screens' });
await expect(
screens.getByRole('tab', { name: 'Scene', exact: true }),
).toHaveAccessibleDescription('Your entities drawn in 2D: select and move them.');
await expect(
screens.getByRole('tab', { name: 'Project', exact: true }),
).toHaveAccessibleDescription(/apps and shared libraries/);
});
1 change: 1 addition & 0 deletions apps/editor/src/lib/project/register-core-panels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const PANELS = [
{
id: 'core.project',
title: 'Project',
description: 'Your apps and shared libraries, and what each one uses.',
icon: 'layers',
kind: 'screen',
order: 100,
Expand Down
6 changes: 4 additions & 2 deletions apps/editor/src/lib/workspace/workspace-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {
import { type AppModel, ProjectsContract } from '@nanoforge-dev/editor-protocol';
import type { RpcClient } from '@nanoforge-dev/editor-rpc';
import type { NotificationService, PromptService } from '@nanoforge-dev/editor-ui';
/** The notification helpers alone: the kit's main entry brings its Svelte components. */
import { notifyError } from '@nanoforge-dev/editor-ui/notification';

/** Undo and redo target of the Project screen: changes of the workspace. */
export const PROJECT_HISTORY_CONTEXT = 'project';
Expand Down Expand Up @@ -269,8 +271,8 @@ export class WorkspaceActions implements Disposable {
detail: 'Its output is in the Console, under Tasks.',
});
} catch (error) {
notifications.notify('error', 'Could not install the dependencies', {
detail: error instanceof Error ? error.message : String(error),
notifyError(notifications, 'Could not install the dependencies', error, {
retry: () => this.install(),
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions apps/editor/src/routes/(editor)/project/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
resolve();
});
});
const recent = await editor.projects.recent().catch(() => []);
if (disposed) return;
created = setupWorkbench({
shell: editor.shell,
Expand All @@ -157,6 +158,7 @@
history: editor.history,
logger: editor.services.get(CoreServices.Logger).getLogger('workbench'),
activate: (event) => editor.plugins.activateByEvent(event),
knownUser: recent.some((project) => project.id !== id),
});
provided = [
editor.services.provide(LayoutControllerToken, created.layout),
Expand Down
8 changes: 4 additions & 4 deletions docs/docs/editor/git.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The **Commit** panel on the left lists what changed since the last commit, as fi

1. **Check** the files to commit. A group's checkbox checks all its files.
2. Write a message.
3. **Commit**, or **Commit and Push…**
3. **Commit**, or **Commit and push…**

**Amend** replaces the last commit instead of adding one.

Expand All @@ -27,7 +27,7 @@ The **Commit** panel on the left lists what changed since the last commit, as fi
| Undo your changes to files | Select them, then **Rollback**. It asks first: it cannot be undone. |
| Start tracking a new file | Right-click › Add to VCS |
| Ignore a file | Right-click › Add to .gitignore |
| Put changes aside | The **Stash** tab: Stash Changes, then Apply, Pop or Drop |
| Put changes aside | The **Stash** tab: Stash changes, then Apply, Pop or Drop |

Checking a file only chooses what is committed. Selecting rows (click, `Ctrl`+click, `Shift`+click, or a click on a group to select all its files) is what Rollback and the other actions act on.

Expand All @@ -45,7 +45,7 @@ The status bar shows the branch and what is ahead and behind (`main ↑1 ↓2`).

The **Git** panel at the bottom has three parts:

- **Branches**: the current one, local ones, and remote ones. Double-click a branch to check it out. Right-click for _New Branch from…_, _Update_, _Push…_, _Delete_.
- **Branches**: the current one, local ones, and remote ones. Double-click a branch to check it out. Right-click for _New branch from…_, _Update_, _Push…_, _Delete_.
- **Commits** of the selected branch, with a filter by text, author and date.
- **Changed files** of the selected commit, and its details. Double-click a file to compare.

Expand All @@ -57,7 +57,7 @@ Changed files are colored and carry a mark in the **Files** panel, and folders t

## Conflicts

After an update that conflicts, the files show under **Merge Conflicts**. Open one, fix it in the code editor, then **Mark resolved**.
After an update that conflicts, the files show under **Merge conflicts**. Open one, fix it in the code editor, then **Mark resolved**.

<Note>
Git works on the project's own repository only, and in local editors. Hosted projects are saved to
Expand Down
1 change: 1 addition & 0 deletions docs/docs/editor/layout.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ _View › Layouts_:

- **Save layout** keeps the current arrangement under a name;
- the saved layouts are listed there: choose one to switch to it;
- **Simple layout** shows the main panels only (Files, Hierarchy, Scenes, Inspector, Console), **All panels** every one; each puts the layout back to that default, and becomes the default of _Reset layout_ (the setting _Default layout_). A new editor starts with the simple one.
- **Reset layout** goes back to the default.

Moving, closing, floating and resizing a panel are steps you can undo: `Ctrl+Z` with the focus on the layout, or in the **History** panel under _Layout_.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/editor/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Letters only need to be in order, not next to each other. The commands you ran l

`Ctrl+Z` undoes the last change **of what has focus**: the file you edit, the Files panel, the layout. Each has its own history, so undoing a file move does not undo your code.

The **History** panel lists the steps of what you are working on. Click a step to go back to it; the _All_ switch shows every history.
The **History** panel (_View › Panels › History_) lists the steps of what you are working on. Click a step to go back to it; the _All_ switch shows every history.

## Problems and the status bar

Expand Down
Loading
Loading