Implement milestone 6b workbench routing skeleton
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Shared helpers for Verstak E2E tests.
|
||||
*/
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
/** Wait for the app to finish loading (loading screen disappears) */
|
||||
export async function waitForAppReady(page) {
|
||||
// App shows "Loading Verstak..." initially, then renders the main layout
|
||||
await page.waitForSelector('main', { state: 'visible', timeout: 15000 });
|
||||
// Wait a bit for all async data to load
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
/** Collect all console errors since last reset */
|
||||
export function setupConsoleCollector(page) {
|
||||
const errors = [];
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') {
|
||||
errors.push(msg.text());
|
||||
}
|
||||
});
|
||||
page.on('pageerror', (err) => {
|
||||
errors.push(err.message);
|
||||
});
|
||||
return {
|
||||
getErrors: () => errors,
|
||||
assertNoErrors: () => {
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Console errors detected:\n${errors.join('\n')}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Reset mock state before each test */
|
||||
export async function resetMockState(page) {
|
||||
await page.evaluate(() => {
|
||||
if (window.__wailsMock) {
|
||||
window.__wailsMock.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set plugin status in mock */
|
||||
export async function setPluginStatus(page, pluginId, status, enabled) {
|
||||
await page.evaluate(
|
||||
({ id, st, en }) => {
|
||||
if (window.__wailsMock) {
|
||||
window.__wailsMock.setPluginStatus(id, st, en);
|
||||
}
|
||||
},
|
||||
{ id: pluginId, st: status, en: enabled }
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js';
|
||||
|
||||
test.describe('D: Plugin API bridge', () => {
|
||||
let consoleCollector;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
consoleCollector = setupConsoleCollector(page);
|
||||
await resetMockState(page);
|
||||
await page.goto('/');
|
||||
await waitForAppReady(page);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
consoleCollector.assertNoErrors();
|
||||
});
|
||||
|
||||
test('platform-test reads and writes settings through scoped API after reload', async ({ page }) => {
|
||||
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
||||
|
||||
const saved = page.locator('.pt-saved-setting');
|
||||
await expect(saved).toHaveText('Saved setting: initial value', { timeout: 10000 });
|
||||
|
||||
const input = page.locator('.pt-setting-input');
|
||||
await input.fill('persisted through bridge');
|
||||
await page.locator('.pt-save-setting').click();
|
||||
await expect(saved).toHaveText('Saved setting: persisted through bridge', { timeout: 10000 });
|
||||
|
||||
await page.locator('.sidebar .nav-item').filter({ hasText: 'Plugin Manager' }).click();
|
||||
await expect.poll(() => page.evaluate(() => Object.keys(window.__VERSTAK_COMMAND_HANDLERS__ || {}).length)).toBe(0);
|
||||
await expect.poll(() => page.evaluate(() => (window.__VERSTAK_EVENT_HANDLERS__?.['verstak.platform-test.echo'] || []).length)).toBe(0);
|
||||
await page.locator('button.reload-btn').click();
|
||||
await expect(page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' }).locator('.status-badge')).toHaveText('loaded', { timeout: 10000 });
|
||||
|
||||
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
||||
await expect(page.locator('.pt-saved-setting')).toHaveText('Saved setting: persisted through bridge', { timeout: 10000 });
|
||||
await expect(page.locator('.pt-badge')).toHaveAttribute('data-command-status', 'handled');
|
||||
await expect(page.locator('.pt-badge')).toContainText('capability available');
|
||||
await expect(page.locator('.pt-command-result')).toContainText('Command: handled 0.1.0 from bundled-frontend');
|
||||
await expect(page.locator('.pt-event-result')).toHaveAttribute('data-event-status', 'received');
|
||||
await expect(page.locator('.pt-event-result')).toContainText('Event: received hello-event');
|
||||
await expect(page.locator('.pt-files-result')).toHaveAttribute('data-files-status', 'ok');
|
||||
await expect(page.locator('.pt-files-result')).toContainText('Files: wrote/read/listed/moved/trashed');
|
||||
await expect(page.locator('.pt-files-error-result')).toHaveAttribute('data-files-error-status', 'expected');
|
||||
await expect(page.locator('.pt-files-error-result')).toContainText('Files error path: rejected reserved-path');
|
||||
|
||||
await page.locator('.pt-open-workbench-notes').click();
|
||||
await expect(page.locator('.pt-workbench-result')).toHaveAttribute('data-workbench-status', 'ok');
|
||||
await expect(page.locator('.pt-workbench-result')).toContainText('Workbench: opened Notes/Overview.md with verstak.platform-test.markdown-diagnostic');
|
||||
await expect(page.locator('.pt-workbench-result')).toHaveAttribute('data-resource-context', 'notes-markdown');
|
||||
});
|
||||
|
||||
test('platform-test diagnostic provider routes text, markdown, and notes markdown contexts', async ({ page }) => {
|
||||
async function openFromDiagnostics(buttonClass, path, context) {
|
||||
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
||||
await expect(page.locator('.pt-command-result')).toContainText('Command: handled', { timeout: 10000 });
|
||||
await page.locator(buttonClass).click();
|
||||
const result = page.locator('.pt-workbench-result');
|
||||
await expect(result).toHaveAttribute('data-workbench-status', 'ok', { timeout: 10000 });
|
||||
await expect(result).toHaveAttribute('data-resource-path', path);
|
||||
await expect(result).toHaveAttribute('data-resource-mode', 'edit');
|
||||
await expect(result).toHaveAttribute('data-resource-context', context);
|
||||
await expect(result).toContainText('verstak.platform-test.markdown-diagnostic');
|
||||
}
|
||||
|
||||
await openFromDiagnostics('.pt-open-workbench-text', 'Docs/todo.txt', 'generic-text');
|
||||
await openFromDiagnostics('.pt-open-workbench-markdown', 'Docs/readme.md', 'generic-markdown');
|
||||
await openFromDiagnostics('.pt-open-workbench-notes', 'Notes/Overview.md', 'notes-markdown');
|
||||
});
|
||||
|
||||
test('workbench shows no-provider fallback when no provider matches', async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.platform-test', {
|
||||
kind: 'vault-file',
|
||||
path: 'Images/logo.png',
|
||||
extension: '.png',
|
||||
context: { sourceView: 'files' },
|
||||
});
|
||||
if (err) throw new Error(err);
|
||||
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||
});
|
||||
|
||||
await expect(page.locator('[data-workbench-status="no-provider"]')).toBeVisible();
|
||||
await expect(page.locator('[data-workbench-status="no-provider"]')).toContainText('No viewer/editor available');
|
||||
});
|
||||
|
||||
test('platform-test command and event handlers are cleaned up after leaving plugin view', async ({ page }) => {
|
||||
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
||||
|
||||
await expect(page.locator('.pt-command-result')).toContainText('Command: handled', { timeout: 10000 });
|
||||
await expect(page.locator('.pt-event-result')).toHaveAttribute('data-event-status', 'received', { timeout: 10000 });
|
||||
await expect.poll(() => page.evaluate(() => Object.keys(window.__VERSTAK_COMMAND_HANDLERS__ || {}).length)).toBe(1);
|
||||
await expect.poll(() => page.evaluate(() => (window.__VERSTAK_EVENT_HANDLERS__?.['verstak.platform-test.echo'] || []).length)).toBe(1);
|
||||
|
||||
await page.locator('.sidebar .nav-item').filter({ hasText: 'Plugin Manager' }).click();
|
||||
|
||||
await expect.poll(() => page.evaluate(() => Object.keys(window.__VERSTAK_COMMAND_HANDLERS__ || {}).length)).toBe(0);
|
||||
await expect.poll(() => page.evaluate(() => (window.__VERSTAK_EVENT_HANDLERS__?.['verstak.platform-test.echo'] || []).length)).toBe(0);
|
||||
});
|
||||
|
||||
test('platform-test cleanup remains empty after disable reload flow', async ({ page }) => {
|
||||
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
||||
await expect(page.locator('.pt-command-result')).toContainText('Command: handled', { timeout: 10000 });
|
||||
|
||||
await page.locator('.sidebar .nav-item').filter({ hasText: 'Plugin Manager' }).click();
|
||||
const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' });
|
||||
await pluginCard.locator('button.btn-disable').click();
|
||||
await expect(pluginCard.locator('button.btn-enable')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await expect.poll(() => page.evaluate(() => Object.keys(window.__VERSTAK_COMMAND_HANDLERS__ || {}).length)).toBe(0);
|
||||
await expect.poll(() => page.evaluate(() => (window.__VERSTAK_EVENT_HANDLERS__?.['verstak.platform-test.echo'] || []).length)).toBe(0);
|
||||
});
|
||||
|
||||
test('platform-test settings panel loads bundle content returned as raw string', async ({ page }) => {
|
||||
await page.locator('.sidebar .nav-item').filter({ hasText: 'Plugin Manager' }).click();
|
||||
|
||||
const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' });
|
||||
await pluginCard.locator('button.btn-settings').click();
|
||||
|
||||
const modal = page.locator('.modal[aria-label="Plugin Settings"]');
|
||||
await expect(modal).toBeVisible();
|
||||
await expect(modal).toContainText('Platform Test Settings');
|
||||
await expect(modal.locator('.host-state.error')).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Acceptance Test A: Plugin Manager Disable/Enable refresh
|
||||
*
|
||||
* Scenario:
|
||||
* 1. Open Plugin Manager
|
||||
* 2. See Platform Test as loaded/enabled
|
||||
* 3. Click Disable
|
||||
* 4. Verify Enable button appears
|
||||
* 5. Verify plugin sidebar item disappears
|
||||
* 6. Click Enable
|
||||
* 7. Verify Disable button appears
|
||||
* 8. Verify plugin sidebar item returns
|
||||
*/
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js';
|
||||
|
||||
test.describe('A: Plugin Manager Disable/Enable refresh', () => {
|
||||
let consoleCollector;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
consoleCollector = setupConsoleCollector(page);
|
||||
await resetMockState(page);
|
||||
await page.goto('/');
|
||||
await waitForAppReady(page);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
consoleCollector.assertNoErrors();
|
||||
});
|
||||
|
||||
test('Platform Test plugin is initially visible and enabled', async ({ page }) => {
|
||||
// Plugin Manager should show Platform Test plugin card
|
||||
const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' });
|
||||
await expect(pluginCard).toBeVisible();
|
||||
|
||||
// Status should show "loaded"
|
||||
const statusBadge = pluginCard.locator('.status-badge');
|
||||
await expect(statusBadge).toHaveText('loaded');
|
||||
|
||||
// Disable button should be visible (not Enable)
|
||||
const disableBtn = pluginCard.locator('button.btn-disable');
|
||||
await expect(disableBtn).toBeVisible();
|
||||
|
||||
// Sidebar should have Platform Test item
|
||||
const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' });
|
||||
await expect(sidebarItem).toBeVisible();
|
||||
});
|
||||
|
||||
test('Disable plugin: button changes to Enable, sidebar item disappears', async ({ page }) => {
|
||||
const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' });
|
||||
|
||||
// Click Disable
|
||||
const disableBtn = pluginCard.locator('button.btn-disable');
|
||||
await expect(disableBtn).toBeVisible();
|
||||
await disableBtn.click();
|
||||
|
||||
// Wait for UI to update after disable
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// After disable: Enable button should appear
|
||||
const enableBtn = pluginCard.locator('button.btn-enable');
|
||||
await expect(enableBtn).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// After disable: sidebar item for this plugin should disappear
|
||||
const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' });
|
||||
await expect(sidebarItem).not.toBeVisible();
|
||||
|
||||
// Status should show "disabled"
|
||||
const statusBadge = pluginCard.locator('.status-badge');
|
||||
await expect(statusBadge).toHaveText('disabled');
|
||||
});
|
||||
|
||||
test('Re-enable plugin: button changes to Disable, sidebar item returns', async ({ page }) => {
|
||||
const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' });
|
||||
|
||||
// First disable
|
||||
await pluginCard.locator('button.btn-disable').click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Wait for Enable button
|
||||
const enableBtn = pluginCard.locator('button.btn-enable');
|
||||
await expect(enableBtn).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click Enable
|
||||
await enableBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// After re-enable: Disable button should appear
|
||||
const disableBtn = pluginCard.locator('button.btn-disable');
|
||||
await expect(disableBtn).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Sidebar item should return
|
||||
const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' });
|
||||
await expect(sidebarItem).toBeVisible();
|
||||
|
||||
// Status should show "loaded"
|
||||
const statusBadge = pluginCard.locator('.status-badge');
|
||||
await expect(statusBadge).toHaveText('loaded');
|
||||
});
|
||||
|
||||
test('Disable → Enable full flow in sequence', async ({ page }) => {
|
||||
const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' });
|
||||
const sidebar = page.locator('.sidebar');
|
||||
|
||||
// Initial state: enabled
|
||||
await expect(pluginCard.locator('button.btn-disable')).toBeVisible();
|
||||
await expect(sidebar.locator('.plugin-item').filter({ hasText: 'Platform Test' })).toBeVisible();
|
||||
|
||||
// Disable
|
||||
await pluginCard.locator('button.btn-disable').click();
|
||||
await page.waitForTimeout(500);
|
||||
await expect(pluginCard.locator('button.btn-enable')).toBeVisible({ timeout: 10000 });
|
||||
await expect(sidebar.locator('.plugin-item').filter({ hasText: 'Platform Test' })).not.toBeVisible();
|
||||
|
||||
// Enable
|
||||
await pluginCard.locator('button.btn-enable').click();
|
||||
await page.waitForTimeout(500);
|
||||
await expect(pluginCard.locator('button.btn-disable')).toBeVisible({ timeout: 10000 });
|
||||
await expect(sidebar.locator('.plugin-item').filter({ hasText: 'Platform Test' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js';
|
||||
|
||||
test.describe('E: Plugin Manager layout', () => {
|
||||
let consoleCollector;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
consoleCollector = setupConsoleCollector(page);
|
||||
await resetMockState(page);
|
||||
await page.goto('/');
|
||||
await waitForAppReady(page);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
consoleCollector.assertNoErrors();
|
||||
});
|
||||
|
||||
test('plugin list scrolls through the global main scroll surface and stays responsive', async ({ page }) => {
|
||||
await page.evaluate(() => window.__wailsMock.addSyntheticPlugins(18));
|
||||
await page.locator('button.reload-btn').click();
|
||||
await expect(page.locator('.plugin-card')).toHaveCount(19, { timeout: 10000 });
|
||||
|
||||
const manager = page.locator('.plugin-manager');
|
||||
const scrollSurface = page.locator('.content.scroll-surface');
|
||||
await expect(manager).toBeVisible();
|
||||
await expect(scrollSurface).toBeVisible();
|
||||
|
||||
const desktopMetrics = await scrollSurface.evaluate((node) => ({
|
||||
clientHeight: node.clientHeight,
|
||||
scrollHeight: node.scrollHeight,
|
||||
overflowY: getComputedStyle(node).overflowY,
|
||||
}));
|
||||
expect(desktopMetrics.overflowY).toBe('auto');
|
||||
expect(desktopMetrics.scrollHeight).toBeGreaterThan(desktopMetrics.clientHeight);
|
||||
|
||||
const scrolledTop = await scrollSurface.evaluate((node) => {
|
||||
node.scrollTop = node.scrollHeight;
|
||||
return node.scrollTop;
|
||||
});
|
||||
expect(scrolledTop).toBeGreaterThan(0);
|
||||
|
||||
await page.setViewportSize({ width: 720, height: 640 });
|
||||
await expect(manager).toBeVisible();
|
||||
|
||||
const narrowMetrics = await scrollSurface.evaluate((node) => ({
|
||||
clientWidth: node.clientWidth,
|
||||
scrollWidth: node.scrollWidth,
|
||||
scrollTop: node.scrollTop,
|
||||
}));
|
||||
expect(narrowMetrics.scrollWidth).toBeLessThanOrEqual(narrowMetrics.clientWidth + 1);
|
||||
expect(narrowMetrics.scrollTop).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('platform-test buttons use the global button contract', async ({ page }) => {
|
||||
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
||||
|
||||
const saveButton = page.locator('.pt-save-setting');
|
||||
await expect(saveButton).toBeVisible({ timeout: 10000 });
|
||||
await expect(saveButton).toHaveClass(/btn-primary/);
|
||||
|
||||
const buttonStyle = await saveButton.evaluate((node) => {
|
||||
const style = getComputedStyle(node);
|
||||
return {
|
||||
display: style.display,
|
||||
backgroundColor: style.backgroundColor,
|
||||
borderRadius: style.borderRadius,
|
||||
};
|
||||
});
|
||||
expect(buttonStyle.display).toBe('inline-flex');
|
||||
expect(buttonStyle.backgroundColor).not.toBe('rgba(0, 0, 0, 0)');
|
||||
expect(buttonStyle.borderRadius).toBe('6px');
|
||||
});
|
||||
|
||||
test('workspace selection keeps exactly one active node', async ({ page }) => {
|
||||
const selected = page.locator('.wt-node.selected .wt-label');
|
||||
await expect(selected).toHaveCount(1);
|
||||
await expect(selected).toHaveText('Alpha Case');
|
||||
|
||||
await page.locator('.wt-label').filter({ hasText: 'Beta Case' }).click();
|
||||
|
||||
await expect(selected).toHaveCount(1);
|
||||
await expect(selected).toHaveText('Beta Case');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Acceptance Test C: Reload updates UI state
|
||||
*
|
||||
* Scenario:
|
||||
* 1. Change mocked plugin state (e.g. disable a plugin in mock)
|
||||
* 2. Click Reload button
|
||||
* 3. Verify UI reflects the updated state
|
||||
*/
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { waitForAppReady, setupConsoleCollector, resetMockState, setPluginStatus } from './helpers.js';
|
||||
|
||||
test.describe('C: Reload updates UI state', () => {
|
||||
let consoleCollector;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
consoleCollector = setupConsoleCollector(page);
|
||||
await resetMockState(page);
|
||||
await page.goto('/');
|
||||
await waitForAppReady(page);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
consoleCollector.assertNoErrors();
|
||||
});
|
||||
|
||||
test('Reload after mock state change reflects new plugin status', async ({ page }) => {
|
||||
const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' });
|
||||
|
||||
// Initial state: loaded/enabled
|
||||
await expect(pluginCard.locator('.status-badge')).toHaveText('loaded');
|
||||
await expect(pluginCard.locator('button.btn-disable')).toBeVisible();
|
||||
|
||||
// Change mock state to disabled (simulating external state change)
|
||||
await setPluginStatus(page, 'verstak.platform-test', 'disabled', false);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Click Reload button in Plugin Manager header
|
||||
const reloadBtn = page.locator('button.reload-btn');
|
||||
await expect(reloadBtn).toBeVisible();
|
||||
await reloadBtn.click();
|
||||
|
||||
// Wait for reload to complete and UI to update
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// After reload: status should reflect the disabled state
|
||||
await expect(pluginCard.locator('.status-badge')).toHaveText('disabled', { timeout: 10000 });
|
||||
|
||||
// Enable button should appear (since plugin is now disabled)
|
||||
await expect(pluginCard.locator('button.btn-enable')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Sidebar item should be gone (disabled plugins are filtered from sidebar)
|
||||
const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' });
|
||||
await expect(sidebarItem).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Reload restores plugin after re-enabling in mock', async ({ page }) => {
|
||||
const pluginCard = page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' });
|
||||
|
||||
// Disable in mock, reload
|
||||
await setPluginStatus(page, 'verstak.platform-test', 'disabled', false);
|
||||
await page.waitForTimeout(200);
|
||||
await page.locator('button.reload-btn').click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify disabled
|
||||
await expect(pluginCard.locator('.status-badge')).toHaveText('disabled', { timeout: 10000 });
|
||||
await expect(pluginCard.locator('button.btn-enable')).toBeVisible();
|
||||
|
||||
// Re-enable in mock
|
||||
await setPluginStatus(page, 'verstak.platform-test', 'loaded', true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Reload again
|
||||
await page.locator('button.reload-btn').click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// After reload: should be loaded again
|
||||
await expect(pluginCard.locator('.status-badge')).toHaveText('loaded', { timeout: 10000 });
|
||||
await expect(pluginCard.locator('button.btn-disable')).toBeVisible();
|
||||
|
||||
// Sidebar item should return
|
||||
const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' });
|
||||
await expect(sidebarItem).toBeVisible();
|
||||
});
|
||||
|
||||
test('Reload button is not disabled during normal operation', async ({ page }) => {
|
||||
const reloadBtn = page.locator('button.reload-btn');
|
||||
await expect(reloadBtn).toBeVisible();
|
||||
await expect(reloadBtn).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test('Reload handles raw Wails count result without falling into error state', async ({ page }) => {
|
||||
await page.evaluate(() => window.__wailsMock.setReloadResponseMode('raw-count'));
|
||||
|
||||
const reloadBtn = page.locator('button.reload-btn');
|
||||
await reloadBtn.click();
|
||||
|
||||
await expect(page.locator('.error-state')).toHaveCount(0);
|
||||
await expect(page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' })).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Acceptance Test B: Sidebar opens plugin view by item.view, not item.id
|
||||
*
|
||||
* Data:
|
||||
* - sidebar item id = verstak.platform-test.sidebar
|
||||
* - sidebar item view = verstak.platform-test.diagnostics
|
||||
*
|
||||
* Scenario:
|
||||
* 1. Click sidebar item "Platform Test"
|
||||
* 2. Verify diagnostics view is opened (verstak.platform-test.diagnostics)
|
||||
* 3. Verify NOT opened empty container by sidebar id
|
||||
*/
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js';
|
||||
|
||||
test.describe('B: Sidebar opens plugin view by item.view', () => {
|
||||
let consoleCollector;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
consoleCollector = setupConsoleCollector(page);
|
||||
await resetMockState(page);
|
||||
await page.goto('/');
|
||||
await waitForAppReady(page);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
consoleCollector.assertNoErrors();
|
||||
});
|
||||
|
||||
test('Sidebar item exists with correct label', async ({ page }) => {
|
||||
const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' });
|
||||
await expect(sidebarItem).toBeVisible();
|
||||
});
|
||||
|
||||
test('Click sidebar item opens diagnostics view by view ID, not sidebar ID', async ({ page }) => {
|
||||
const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' });
|
||||
await expect(sidebarItem).toBeVisible();
|
||||
|
||||
// Click the sidebar item
|
||||
await sidebarItem.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// View container should be visible
|
||||
const viewContainer = page.locator('.view-container');
|
||||
await expect(viewContainer).toBeVisible();
|
||||
|
||||
// The view header should show "Platform Diagnostics" (from view contribution title)
|
||||
// This proves the view was opened by item.view = "verstak.platform-test.diagnostics"
|
||||
// NOT by item.id = "verstak.platform-test.sidebar"
|
||||
const viewHeader = viewContainer.locator('.view-header h2');
|
||||
await expect(viewHeader).toHaveText('Platform Diagnostics', { timeout: 10000 });
|
||||
|
||||
// The view should NOT show "View ... not found" error
|
||||
// (which would happen if it tried to open by sidebar item id)
|
||||
await expect(viewContainer).not.toHaveText(/not found/);
|
||||
|
||||
// The view should NOT show an empty container message
|
||||
const emptyView = viewContainer.locator('.empty');
|
||||
await expect(emptyView).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('View header shows correct title from view contribution', async ({ page }) => {
|
||||
const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' });
|
||||
await sidebarItem.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify the view title comes from the view contribution (item.view)
|
||||
// NOT from the sidebar item (item.id)
|
||||
const viewHeader = page.locator('.view-container .view-header h2');
|
||||
await expect(viewHeader).toHaveText('Platform Diagnostics', { timeout: 10000 });
|
||||
|
||||
// Should NOT show sidebar item id as view title
|
||||
await expect(viewHeader).not.toHaveText('verstak.platform-test.sidebar');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user