diff --git a/web/playwright-tests/bar-chart-navigation.spec.ts b/web/playwright-tests/bar-chart-navigation.spec.ts index d960cd3d5..18a18b46c 100644 --- a/web/playwright-tests/bar-chart-navigation.spec.ts +++ b/web/playwright-tests/bar-chart-navigation.spec.ts @@ -78,3 +78,58 @@ test('Can open sql editor', async ({ page }) => { await page.getByTestId('edit-chart-button').getByRole('button').click(); await expect(page.locator('div').filter({ hasText: /^Edit chart$/ })).toBeVisible(); }); + +test('Bar chart span clicking works', async ({ page }) => { + await page.goto('http://localhost:3000/app/metrics/45c17750-2b61-5683-ba8d-ff6c6fefacee/chart'); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page.getByTestId('segmented-trigger-results').click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await expect(page.getByTestId('metric-view-chart-content').getByRole('img')).toBeVisible(); + await page.getByTestId('edit-sql-button').getByRole('button').click(); + await page.waitForTimeout(55); + await expect(page.getByText('Copy SQLSaveRun')).toBeVisible(); + await page.getByTestId('segmented-trigger-file').click(); + await page.waitForTimeout(55); + + await expect( + page.getByText('Yearly Sales Revenue - Signature Cycles Products (Last 3 Years + YTD)', { + exact: true + }) + ).toBeVisible(); + + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await expect(page.getByText('Edit chart')).toBeVisible({ timeout: 15000 }); + await page + .locator('div') + .filter({ hasText: /^Edit chart$/ }) + .getByRole('button') + .click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page + .locator('div') + .filter({ hasText: /^Edit chart$/ }) + .getByRole('button') + .click(); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page + .locator('div') + .filter({ hasText: /^Edit chart$/ }) + .getByRole('button') + .click(); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - textbox "New chart": Yearly Sales Revenue - Signature Cycles Products (Last 3 Years + YTD) + - text: /Jan 1, \\d+ - May 2, \\d+ • What is the total yearly sales revenue for products supplied by Signature Cycles from \\d+ to present\\? Total Sales Revenue/ + - img + `); +}); diff --git a/web/playwright-tests/pie-styling-updates.spec.ts b/web/playwright-tests/pie-styling-updates.spec.ts new file mode 100644 index 000000000..a62409ca9 --- /dev/null +++ b/web/playwright-tests/pie-styling-updates.spec.ts @@ -0,0 +1,407 @@ +import { test, expect } from '@playwright/test'; + +test.describe.serial('Pie chart styling updates', async () => { + test('Pie chart - can put string column on x axis', async ({ page }) => { + await page.goto('http://localhost:3000/app/metrics/88f342bf-19f9-53a9-87c6-804399e69644/chart'); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.getByTestId('select-chart-type-pie').click(); + await page.waitForTimeout(250); + const sourceElement = page + .getByTestId('select-axis-available-items-list') + .getByRole('button') + .first(); + const targetElement = page + .getByTestId('select-axis-drop-zone-xAxis') + .locator('div') + .filter({ hasText: /^Drag column here$/ }); + const sourceBoundingBox = await sourceElement.boundingBox(); + const targetBoundingBox = await targetElement.boundingBox(); + + if (sourceBoundingBox && targetBoundingBox) { + // Start at the center of the source element + await page.mouse.move( + sourceBoundingBox.x + sourceBoundingBox.width / 2, + sourceBoundingBox.y + sourceBoundingBox.height / 2 + ); + await page.mouse.down(); + + // Move to target in small increments + const steps = 30; + const dx = (targetBoundingBox.x - sourceBoundingBox.x) / steps; + const dy = (targetBoundingBox.y - sourceBoundingBox.y) / steps; + + for (let i = 0; i <= steps; i++) { + await page.mouse.move( + sourceBoundingBox.x + dx * i + sourceBoundingBox.width / 2, + sourceBoundingBox.y + dy * i + sourceBoundingBox.height / 2, + { steps: 1 } + ); + await page.waitForTimeout(1); // Add a small delay between each movement + } + + await page.mouse.up(); + } + + await expect(page.getByRole('button', { name: 'Reset' })).toBeVisible(); + await page.getByRole('button', { name: 'Save' }).click(); + await page.waitForTimeout(25); + await page.waitForLoadState('networkidle'); + await expect(page.getByRole('button', { name: 'Reset' })).toBeHidden(); + }); + + test('Pie chart span clicking works', async ({ page }) => { + await page.goto('http://localhost:3000/app/metrics/88f342bf-19f9-53a9-87c6-804399e69644/chart'); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page.getByTestId('segmented-trigger-results').click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await expect(page.getByTestId('metric-view-chart-content').getByRole('img')).toBeVisible(); + await page.getByTestId('edit-sql-button').getByRole('button').click(); + await page.waitForTimeout(55); + await expect(page.getByText('Copy SQLSaveRun')).toBeVisible(); + await page.getByTestId('segmented-trigger-file').click(); + await page.waitForTimeout(55); + await expect(page.getByText('Top 10 Products by Revenue (').first()).toBeVisible(); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await expect(page.getByText('Edit chart')).toBeVisible(); + await page + .locator('div') + .filter({ hasText: /^Edit chart$/ }) + .getByRole('button') + .click(); + await page.waitForTimeout(55); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page + .locator('div') + .filter({ hasText: /^Edit chart$/ }) + .getByRole('button') + .click(); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.waitForTimeout(55); + await page + .locator('div') + .filter({ hasText: /^Edit chart$/ }) + .getByRole('button') + .click(); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - textbox "New chart": /Top \\d+ Products by Revenue \\(Last 4 Quarters\\)/ + - text: /Q2 \\d+ - Q1 \\d+ • Which were the top \\d+ products by revenue in the last four completed quarters\\? Touring-\\d+ Yellow, \\d+ Road-\\d+-W Yellow, \\d+ Touring-\\d+ Blue, \\d+ Road-\\d+-W Yellow, \\d+ Next 6/ + - img + `); + }); + + test('Pie chart - legend clicks work', async ({ page }) => { + await page.goto('http://localhost:3000/app/metrics/88f342bf-19f9-53a9-87c6-804399e69644/chart'); + await page.getByText('Touring-1000 Yellow,').click(); + await page.getByText('Road-350-W Yellow, 40').click(); + await page.getByText('Touring-1000 Blue,').click(); + await page.getByText('Road-350-W Yellow, 48').click(); + await expect(page.locator('.dot > .w-4\\.5').first()).toBeVisible(); + await expect(page.locator('div:nth-child(2) > div > .dot > .w-4\\.5')).toBeVisible(); + await page.locator('.dot > .w-4\\.5').first().click(); + await page.getByText('Road-350-W Yellow, 40').click(); + await page.getByText('Touring-1000 Blue,').click(); + await page.getByText('Road-350-W Yellow, 48').click(); + await page.getByText('Next').click(); + await expect( + page + .locator('div') + .filter({ hasText: /^Mountain-200 Silver, 42$/ }) + .nth(2) + ).toBeVisible(); + await expect(page.getByTestId('metric-view-chart-content')).toMatchAriaSnapshot(` + - text: /Touring-\\d+ Yellow, \\d+ Road-\\d+-W Yellow, \\d+ Touring-\\d+ Blue, \\d+ Road-\\d+-W Yellow, \\d+ Next 6/ + - img + `); + }); + + test('Pie chart - can disable tooltip', async ({ page }) => { + await page.goto('http://localhost:3000/app/metrics/88f342bf-19f9-53a9-87c6-804399e69644/chart'); + await page.getByTestId('edit-chart-button').getByRole('button').click(); + await page.getByTestId('select-axis-drop-zone-tooltip').getByRole('button').click(); + await expect(page.getByRole('switch')).toHaveAttribute('data-state', 'unchecked'); + await page.getByRole('switch').click(); + await expect(page.getByRole('switch')).toHaveAttribute('data-state', 'checked'); + await expect(page.getByRole('button', { name: 'Save' })).toBeVisible(); + await page.getByRole('button', { name: 'Save' }).click(); + await page.waitForTimeout(25); + await page.waitForLoadState('networkidle'); + + await page.reload(); + await page.getByTestId('select-axis-drop-zone-tooltip').getByRole('button').click(); + await expect(page.getByRole('switch')).toHaveAttribute('data-state', 'checked'); + await page.getByRole('switch').click(); + await page.getByRole('button', { name: 'Save' }).click(); + await page.waitForTimeout(25); + await page.waitForLoadState('networkidle'); + }); + + test('Pie chart - styling updates - can change label as', async ({ page }) => { + await page.goto( + 'http://localhost:3000/app/metrics/88f342bf-19f9-53a9-87c6-804399e69644/chart?secondary_view=chart-edit' + ); + await page.getByTestId('segmented-trigger-Styling').click(); + await expect(page.getByTestId('segmented-trigger-number')).toHaveAttribute( + 'data-state', + 'active' + ); + await expect( + page + .locator('div') + .filter({ hasText: /^Show label$/ }) + .getByRole('switch') + ).toHaveAttribute('data-state', 'unchecked'); + await page.getByTestId('segmented-trigger-percent').click(); + await page + .locator('div') + .filter({ hasText: /^Show label$/ }) + .getByRole('switch') + .click(); + await expect(page.getByTestId('segmented-trigger-percent')).toHaveAttribute( + 'data-state', + 'active' + ); + await page.getByRole('button', { name: 'Reset' }).click(); + await expect(page.getByTestId('segmented-trigger-number')).toHaveAttribute( + 'data-state', + 'active' + ); + await page.getByTestId('segmented-trigger-pie').click(); + await expect(page.getByTestId('segmented-trigger-pie')).toBeVisible(); + await page.getByTestId('segmented-trigger-donut').click(); + await page.waitForTimeout(10); + await expect(page.getByTestId('segmented-trigger-donut')).toBeVisible(); + await expect(page.getByText('Donut width')).toBeVisible(); + await page.getByTestId('segmented-trigger-pie').click(); + await expect(page.getByText('Donut width')).not.toBeVisible(); + await page.getByRole('button', { name: 'Save' }).click(); + await page.waitForTimeout(25); + await page.waitForLoadState('networkidle'); + + await page.reload(); + await page.getByTestId('segmented-trigger-Styling').click(); + await expect(page.getByText('Donut width')).not.toBeVisible(); + await page.getByTestId('segmented-trigger-donut').click(); + await expect(page.getByText('Donut width')).toBeVisible(); + await page.getByRole('button', { name: 'Save' }).click(); + await page.waitForTimeout(25); + await page.waitForLoadState('networkidle'); + }); + + test('Donut chart - minimum slice percentage', async ({ page }) => { + await page.goto( + 'http://localhost:3000/app/metrics/88f342bf-19f9-53a9-87c6-804399e69644/chart?secondary_view=chart-edit' + ); + await page.getByTestId('segmented-trigger-Styling').click(); + await page + .locator('div') + .filter({ hasText: /^Minimum slice %$/ }) + .getByRole('spinbutton') + .click(); + await page + .locator('div') + .filter({ hasText: /^Minimum slice %$/ }) + .getByRole('spinbutton') + .press('ArrowLeft'); + await page + .locator('div') + .filter({ hasText: /^Minimum slice %$/ }) + .getByRole('spinbutton') + .fill('80'); + await expect(page.getByText('Mountain-200 Black,')).toBeVisible(); + await page + .locator('div') + .filter({ hasText: /^Minimum slice %$/ }) + .getByRole('spinbutton') + .click(); + await page + .locator('div') + .filter({ hasText: /^Minimum slice %$/ }) + .getByRole('spinbutton') + .fill('10'); + await expect(page.getByText('Mountain-200 Silver,')).toBeVisible(); + await expect(page.getByText('Mountain-200 Black, 42')).toBeVisible(); + await page.getByRole('button', { name: 'Reset' }).click(); + await expect( + page.getByTestId('metric-view-chart-content').getByText('Touring-1000 Yellow,') + ).toBeVisible(); + }); + + test('Donut chart - inner label location', async ({ page }) => { + await page.goto( + 'http://localhost:3000/app/metrics/88f342bf-19f9-53a9-87c6-804399e69644/chart?secondary_view=chart-edit' + ); + await page.getByTestId('segmented-trigger-Styling').click(); + await expect(page.getByText('Touring-1000 Yellow,')).toBeVisible(); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - textbox "New chart": /Top \\d+ Products by Revenue \\(Last 4 Quarters\\)/ + - text: /Q2 \\d+ - Q1 \\d+ • Which were the top \\d+ products by revenue in the last four completed quarters\\? Touring-\\d+ Yellow, \\d+ Road-\\d+-W Yellow, \\d+ Touring-\\d+ Blue, \\d+ Next 7/ + - img + `); + await expect(page.locator('body')).toContainText('Sum'); + await page.locator('html').click(); + await page.getByRole('combobox').filter({ hasText: 'Sum' }).click(); + await page.getByRole('option', { name: 'Median' }).click(); + await expect( + page + .locator('div') + .filter({ hasText: /^Title$/ }) + .getByRole('textbox') + ).toHaveValue('Median'); + await page.getByRole('button', { name: 'Save' }).click(); + + await page.waitForTimeout(25); + await page.waitForLoadState('networkidle'); + + await page.reload(); + await page.getByTestId('segmented-trigger-Styling').click(); + await expect( + page + .locator('div') + .filter({ hasText: /^Title$/ }) + .getByRole('textbox') + ).toHaveValue('Median'); + await expect(page.getByRole('combobox').filter({ hasText: 'Median' })).toBeVisible(); + await page + .locator('div') + .filter({ hasText: /^Show inner label$/ }) + .getByRole('switch') + .click(); + await expect(page.getByRole('combobox').filter({ hasText: 'Median' })).not.toBeVisible(); + + await page.getByRole('switch').nth(2).click(); + await expect( + page + .locator('div') + .filter({ hasText: /^Title$/ }) + .getByRole('textbox') + ).toHaveValue('Median'); + await page.getByRole('combobox').filter({ hasText: 'Median' }).click(); + await page.getByRole('option', { name: 'Sum' }).click(); + await expect( + page + .locator('div') + .filter({ hasText: /^Title$/ }) + .getByRole('textbox') + ).toHaveValue('Sum'); + await page.getByRole('button', { name: 'Reset' }).click(); + await expect( + page + .locator('div') + .filter({ hasText: /^Title$/ }) + .getByRole('textbox') + ).toHaveValue('Median'); + await page.getByRole('combobox').filter({ hasText: 'Median' }).click(); + await page.getByRole('option', { name: 'Sum' }).click(); + await page + .locator('div') + .filter({ hasText: /^Title$/ }) + .getByRole('textbox') + .click(); + await page + .locator('div') + .filter({ hasText: /^Title$/ }) + .getByRole('textbox') + .press('ControlOrMeta+a'); + await page + .locator('div') + .filter({ hasText: /^Title$/ }) + .getByRole('textbox') + .fill('Total'); + await page.getByRole('button', { name: 'Save' }).click(); + + await page.waitForTimeout(25); + await page.waitForLoadState('networkidle'); + }); + + test('Donut chart - legend headers', async ({ page }) => { + await page.goto( + 'http://localhost:3000/app/metrics/88f342bf-19f9-53a9-87c6-804399e69644/chart?secondary_view=chart-edit' + ); + await page.getByTestId('segmented-trigger-Styling').click(); + await page + .locator('div') + .filter({ hasText: /^Show legend$/ }) + .getByRole('switch') + .click(); + await expect( + page + .locator('div') + .filter({ hasText: /^Show legend$/ }) + .getByRole('switch') + ).toHaveAttribute('data-state', 'unchecked'); + + await page.getByRole('combobox').filter({ hasText: 'None' }).click(); + await page.getByRole('option', { name: 'Current' }).click(); + await expect( + page.locator('#metric-chart-container-88f342bf-19f9-53a9-87c6-804399e69644') + ).toContainText('$1,357,446.78'); + await page.getByRole('button', { name: 'Reset' }).click(); + await expect( + page + .locator('div') + .filter({ hasText: /^Show legend$/ }) + .getByRole('switch') + ).toHaveAttribute('data-state', 'checked'); + }); + + test('Donut chart - reset', async ({ page }) => { + await page.goto( + 'http://localhost:3000/app/metrics/88f342bf-19f9-53a9-87c6-804399e69644/chart?secondary_view=chart-edit' + ); + await expect(page.getByTestId('metric-view-chart-content').getByRole('img')).toBeVisible(); + await page.getByTestId('select-axis-drop-zone-xAxis').getByRole('button').nth(1).click(); + await expect(page.getByText('No valid axis selected')).toBeVisible(); + await page.getByTestId('select-chart-type-column').click(); + + await page.waitForTimeout(55); + const sourceElement = page + .getByTestId('select-axis-available-items-list') + .getByRole('button') + .first(); + const targetElement = page + .getByTestId('select-axis-drop-zone-xAxis') + .locator('div') + .filter({ hasText: /^Drag column here$/ }); + const sourceBoundingBox = await sourceElement.boundingBox(); + const targetBoundingBox = await targetElement.boundingBox(); + + if (sourceBoundingBox && targetBoundingBox) { + // Start at the center of the source element + await page.mouse.move( + sourceBoundingBox.x + sourceBoundingBox.width / 2, + sourceBoundingBox.y + sourceBoundingBox.height / 2 + ); + await page.mouse.down(); + + // Move to target in small increments + const steps = 30; + const dx = (targetBoundingBox.x - sourceBoundingBox.x) / steps; + const dy = (targetBoundingBox.y - sourceBoundingBox.y) / steps; + + for (let i = 0; i <= steps; i++) { + await page.mouse.move( + sourceBoundingBox.x + dx * i + sourceBoundingBox.width / 2, + sourceBoundingBox.y + dy * i + sourceBoundingBox.height / 2, + { steps: 1 } + ); + await page.waitForTimeout(1); // Add a small delay between each movement + } + + await page.mouse.up(); + } + await page.waitForTimeout(55); + await expect(page.getByRole('button', { name: 'Reset' })).toBeVisible(); + await page.getByRole('button', { name: 'Save' }).click(); + await page.waitForTimeout(25); + await page.waitForLoadState('networkidle'); + }); +}); diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieLabelLocation.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieLabelLocation.tsx index 9dc547535..900e03c32 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieLabelLocation.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieLabelLocation.tsx @@ -21,7 +21,7 @@ export const EditPieLabelLocation = React.memo( const selectedLabelPosition = useMemo(() => { return options.find((option) => option.value === pieLabelPosition)?.value || 'outside'; }, [pieLabelPosition]); - const hideLabel = pieLabelPosition === 'none'; + const hideLabel = pieLabelPosition === 'none' || !pieLabelPosition; const onChangeSelect = useMemoizedFn((value: IBusterMetricChartConfig['pieLabelPosition']) => { onUpdateChartConfig({ pieLabelPosition: value });