311 lines
12 KiB
TypeScript
311 lines
12 KiB
TypeScript
import { test, expect } from './mind-elixir-test'
|
|
|
|
const data = {
|
|
nodeData: {
|
|
topic: 'Root Topic',
|
|
id: 'root',
|
|
children: [
|
|
{
|
|
id: 'left-main',
|
|
topic: 'Left Main',
|
|
children: [
|
|
{
|
|
id: 'left-child-1',
|
|
topic: 'Left Child 1',
|
|
},
|
|
{
|
|
id: 'left-child-2',
|
|
topic: 'Left Child 2',
|
|
},
|
|
{
|
|
id: 'left-child-3',
|
|
topic: 'Left Child 3',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'right-main',
|
|
topic: 'Right Main',
|
|
children: [
|
|
{
|
|
id: 'right-child-1',
|
|
topic: 'Right Child 1',
|
|
},
|
|
{
|
|
id: 'right-child-2',
|
|
topic: 'Right Child 2',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
}
|
|
|
|
test.beforeEach(async ({ me }) => {
|
|
await me.init(data)
|
|
})
|
|
|
|
test('Create summary for single node', async ({ page, me }) => {
|
|
// Select a single child node
|
|
await me.click('Left Child 1')
|
|
|
|
// Get position of the selected node
|
|
const leftChild1 = page.getByText('Left Child 1', { exact: true })
|
|
const nodeBox = await leftChild1.boundingBox()
|
|
|
|
// Right click to open context menu
|
|
await leftChild1.click({ button: 'right', force: true })
|
|
|
|
// Click summary option in context menu
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Verify summary SVG group appears
|
|
await expect(page.locator('svg g[id^="s-"]')).toBeVisible()
|
|
|
|
// Verify summary text label is visible
|
|
await expect(page.locator('svg g[id^="s-"] text')).toBeVisible()
|
|
await expect(page.locator('svg g[id^="s-"] text')).toHaveText('summary')
|
|
|
|
// Verify summary path (bracket shape) is visible
|
|
await expect(page.locator('svg g[id^="s-"] path')).toBeVisible()
|
|
|
|
// Verify summary is positioned near the selected node
|
|
const summaryPath = page.locator('svg g[id^="s-"] path')
|
|
const summaryBox = await summaryPath.boundingBox()
|
|
|
|
// Summary should be vertically aligned with the selected node (with some tolerance)
|
|
expect(Math.abs(summaryBox!.y - nodeBox!.y)).toBeLessThan(50)
|
|
})
|
|
|
|
test('Create summary for multiple nodes', async ({ page, me }) => {
|
|
// Use drag selection to select multiple child nodes
|
|
await me.dragSelect('Left Child 1', 'Left Child 3')
|
|
|
|
// Get positions of the selected nodes before creating summary
|
|
const leftChild1 = page.getByText('Left Child 1', { exact: true })
|
|
const leftChild2 = page.getByText('Left Child 2', { exact: true })
|
|
const leftChild3 = page.getByText('Left Child 3', { exact: true })
|
|
|
|
const node1Box = await leftChild1.boundingBox()
|
|
const node2Box = await leftChild2.boundingBox()
|
|
const node3Box = await leftChild3.boundingBox()
|
|
|
|
// Right click to open context menu on one of the selected nodes
|
|
await leftChild1.click({ button: 'right', force: true })
|
|
|
|
// Click summary option in context menu
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Verify summary appears
|
|
await expect(page.locator('svg g[id^="s-"]')).toBeVisible()
|
|
await expect(page.locator('svg g[id^="s-"] text')).toHaveText('summary')
|
|
await expect(page.locator('svg g[id^="s-"] path')).toBeVisible()
|
|
|
|
// Verify summary bracket spans across all three selected nodes
|
|
const summaryPath = page.locator('svg g[id^="s-"] path')
|
|
const summaryBox = await summaryPath.boundingBox()
|
|
|
|
// Summary should span from the top of the first node to the bottom of the last node
|
|
const topMostNode = Math.min(node1Box!.y, node2Box!.y, node3Box!.y)
|
|
const bottomMostNode = Math.max(node1Box!.y + node1Box!.height, node2Box!.y + node2Box!.height, node3Box!.y + node3Box!.height)
|
|
|
|
// Summary bracket should cover the vertical range of all selected nodes
|
|
// Allow some tolerance for padding and bracket styling
|
|
expect(summaryBox!.y).toBeLessThanOrEqual(topMostNode + 10)
|
|
expect(summaryBox!.y + summaryBox!.height).toBeGreaterThanOrEqual(bottomMostNode - 10)
|
|
})
|
|
|
|
test('Select and highlight summary', async ({ page, me }) => {
|
|
// Create a summary first
|
|
await me.click('Left Child 1')
|
|
await page.getByText('Left Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Wait for edit mode to finish (press Enter to complete editing)
|
|
await page.keyboard.press('Enter')
|
|
await expect(page.locator('#input-box')).toBeHidden()
|
|
|
|
// Click on the summary to select it
|
|
await page.locator('svg g[id^="s-"]').click()
|
|
|
|
// Verify selection rectangle appears
|
|
await expect(page.locator('svg g[id^="s-"] rect')).toBeVisible()
|
|
})
|
|
|
|
test('Edit summary text', async ({ page, me }) => {
|
|
// Create a summary first
|
|
await me.click('Left Child 1')
|
|
await page.getByText('Left Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Summary creation automatically enters edit mode, so input box should already be visible
|
|
await expect(page.locator('#input-box')).toBeVisible()
|
|
|
|
// Clear existing text and type new text
|
|
await page.keyboard.press('Control+a')
|
|
await page.keyboard.insertText('Custom Summary')
|
|
await page.keyboard.press('Enter')
|
|
|
|
// Verify input box disappears
|
|
await expect(page.locator('#input-box')).toBeHidden()
|
|
|
|
// Verify new text is displayed
|
|
await expect(page.locator('svg g[id^="s-"] text')).toHaveText('Custom Summary')
|
|
})
|
|
|
|
test('Remove summary', async ({ page, me }) => {
|
|
// Create a summary first
|
|
await me.click('Left Child 1')
|
|
await page.getByText('Left Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Wait for edit mode to finish (press Enter to complete editing)
|
|
await page.keyboard.press('Enter')
|
|
await expect(page.locator('#input-box')).toBeHidden()
|
|
|
|
// Verify summary exists
|
|
await expect(page.locator('svg g[id^="s-"]')).toBeVisible()
|
|
|
|
// Select and delete summary
|
|
await page.locator('svg g[id^="s-"]').click()
|
|
await page.keyboard.press('Delete')
|
|
|
|
// Verify summary is removed
|
|
await expect(page.locator('svg g[id^="s-"]')).not.toBeVisible()
|
|
})
|
|
|
|
test('Cannot create summary on root node', async ({ page, me }) => {
|
|
// Try to select root node
|
|
await me.click('Root Topic')
|
|
|
|
// Try to create summary via right click menu
|
|
await page.getByText('Root Topic', { exact: true }).click({ button: 'right', force: true })
|
|
|
|
// Verify summary option is not available or doesn't work for root
|
|
// (The context menu might not show summary option for root, or it might be disabled)
|
|
const summaryOption = page.locator('#cm-summary')
|
|
if (await summaryOption.isVisible()) {
|
|
await summaryOption.click()
|
|
}
|
|
|
|
// Verify no summary is created
|
|
await expect(page.locator('svg g[id^="s-"]')).not.toBeVisible()
|
|
})
|
|
|
|
test('Summary appears on correct side for left branch', async ({ page, me }) => {
|
|
// Select node on left side
|
|
await me.click('Left Child 1')
|
|
await page.getByText('Left Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Get the summary group
|
|
const summaryGroup = page.locator('svg g[id^="s-"]')
|
|
await expect(summaryGroup).toBeVisible()
|
|
|
|
// Verify text anchor is 'end' for left side (text should be right-aligned)
|
|
const summaryText = summaryGroup.locator('text')
|
|
await expect(summaryText).toHaveAttribute('text-anchor', 'end')
|
|
})
|
|
|
|
test('Summary appears on correct side for right branch', async ({ page, me }) => {
|
|
// Select node on right side
|
|
await me.click('Right Child 1')
|
|
await page.getByText('Right Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Get the summary group
|
|
const summaryGroup = page.locator('svg g[id^="s-"]')
|
|
await expect(summaryGroup).toBeVisible()
|
|
|
|
// Verify text anchor is 'start' for right side (text should be left-aligned)
|
|
const summaryText = summaryGroup.locator('text')
|
|
await expect(summaryText).toHaveAttribute('text-anchor', 'start')
|
|
})
|
|
|
|
test('Multiple summaries can coexist', async ({ page, me }) => {
|
|
// Create first summary
|
|
await me.click('Left Child 1')
|
|
await page.getByText('Left Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Create second summary
|
|
await me.click('Right Child 1')
|
|
await page.getByText('Right Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Verify both summaries exist
|
|
const summaryGroups = page.locator('svg g[id^="s-"]')
|
|
await expect(summaryGroups).toHaveCount(2)
|
|
|
|
// Verify both have text elements
|
|
await expect(page.locator('svg g[id^="s-"] text')).toHaveCount(2)
|
|
})
|
|
|
|
test('Summary covers exact range of selected nodes', async ({ page, me }) => {
|
|
// Use drag selection to select all three child nodes
|
|
await me.dragSelect('Left Child 1', 'Left Child 3')
|
|
|
|
// Create summary
|
|
await page.getByText('Left Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
// Finish editing
|
|
await page.keyboard.press('Enter')
|
|
await expect(page.locator('#input-box')).toBeHidden()
|
|
|
|
// Get positions of all child nodes
|
|
const child1Box = await page.getByText('Left Child 1', { exact: true }).boundingBox()
|
|
const child2Box = await page.getByText('Left Child 2', { exact: true }).boundingBox()
|
|
const child3Box = await page.getByText('Left Child 3', { exact: true }).boundingBox()
|
|
|
|
// Get summary bracket position
|
|
const summaryBox = await page.locator('svg g[id^="s-"] path').boundingBox()
|
|
|
|
// Summary should span from Child 1 to Child 3 (including Child 2 in between)
|
|
// This verifies that the summary covers the range correctly
|
|
const topNode = Math.min(child1Box!.y, child2Box!.y, child3Box!.y)
|
|
const bottomNode = Math.max(child1Box!.y + child1Box!.height, child2Box!.y + child2Box!.height, child3Box!.y + child3Box!.height)
|
|
|
|
// Summary bracket should cover the range including all selected nodes
|
|
expect(summaryBox!.y).toBeLessThanOrEqual(topNode + 10)
|
|
expect(summaryBox!.y + summaryBox!.height).toBeGreaterThanOrEqual(bottomNode - 10)
|
|
|
|
// Verify that all three children are within the summary range
|
|
expect(summaryBox!.y).toBeLessThanOrEqual(child1Box!.y + 10)
|
|
expect(summaryBox!.y).toBeLessThanOrEqual(child2Box!.y + 10)
|
|
expect(summaryBox!.y).toBeLessThanOrEqual(child3Box!.y + 10)
|
|
expect(summaryBox!.y + summaryBox!.height).toBeGreaterThanOrEqual(child1Box!.y + child1Box!.height - 10)
|
|
expect(summaryBox!.y + summaryBox!.height).toBeGreaterThanOrEqual(child2Box!.y + child2Box!.height - 10)
|
|
expect(summaryBox!.y + summaryBox!.height).toBeGreaterThanOrEqual(child3Box!.y + child3Box!.height - 10)
|
|
})
|
|
|
|
test('Summary selection state management', async ({ page, me }) => {
|
|
// Create two summaries
|
|
await me.click('Left Child 1')
|
|
await page.getByText('Left Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
await me.click('Right Child 1')
|
|
await page.getByText('Right Child 1', { exact: true }).click({ button: 'right', force: true })
|
|
await page.locator('#cm-summary').click()
|
|
|
|
const summaryGroups = page.locator('svg g[id^="s-"]')
|
|
const firstSummary = summaryGroups.first()
|
|
const secondSummary = summaryGroups.last()
|
|
|
|
// Select first summary
|
|
await firstSummary.click()
|
|
await expect(firstSummary.locator('rect')).toBeVisible()
|
|
await expect(secondSummary.locator('rect')).not.toBeVisible()
|
|
|
|
// Select second summary
|
|
await secondSummary.click()
|
|
await expect(firstSummary.locator('rect')).not.toBeVisible()
|
|
await expect(secondSummary.locator('rect')).toBeVisible()
|
|
|
|
// Click elsewhere to deselect
|
|
await page.locator('#map').click()
|
|
await expect(firstSummary.locator('rect')).not.toBeVisible()
|
|
await expect(secondSummary.locator('rect')).not.toBeVisible()
|
|
})
|