From de3f3768825046bffbbf483c63d54ae5da0f4008 Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Fri, 21 Nov 2025 16:59:01 +0100 Subject: [PATCH] feat(ui): convert disable/enable workflow menu to JS-less dropdown (#10133) * convert the dropdown (overflow menu) to the JS-less one * the "link" still relies on JS to make a POST, changing this is not in scope of this PR * fixed the weird behavior where opener changes it's color when hovering over the "link" * the bug where the script that refreshes the list once in a while closes an open dropdown is not fixed and is not in scope of this PR * use border in the opener * it might not look as sleek but it is easier to see and better for the user to be able to understand that this is an active intractable element * global dropdown improvements * add rounding rules for dropdowns with only one item - the first such case * add testing for rounding rules * added a devtest page to play with the dropdown component on Preview B: https://codeberg.org/forgejo/forgejo/attachments/1462cdda-71f5-45d0-a206-33bb17740cb8 A: https://codeberg.org/forgejo/forgejo/attachments/d3c265cb-6b77-40c8-9944-d9327f3bec65 B: https://codeberg.org/forgejo/forgejo/attachments/17f17c29-4dcd-4015-b5b9-6d438bd2eb0b A: https://codeberg.org/forgejo/forgejo/attachments/d94e196c-725e-47de-b4de-ed97b148ceb6 B: https://codeberg.org/forgejo/forgejo/attachments/1813ded9-f619-47d9-bf15-ad4bcd3535ab A: https://codeberg.org/forgejo/forgejo/attachments/09042e58-331e-414d-ac8f-0f1091033b7f Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10133 Reviewed-by: Otto --- templates/devtest/dropdown.tmpl | 74 ++++++++++++++++++++++++++ templates/explore/search.tmpl | 2 +- templates/repo/actions/list_inner.tmpl | 24 ++++++--- tests/e2e/actions.test.e2e.ts | 47 ++++++++++++++++ tests/e2e/dropdown.test.e2e.ts | 30 +++++++++++ tests/e2e/e2e_test.go | 2 +- web_src/css/modules/dropdown.css | 3 ++ 7 files changed, 173 insertions(+), 9 deletions(-) create mode 100644 templates/devtest/dropdown.tmpl diff --git a/templates/devtest/dropdown.tmpl b/templates/devtest/dropdown.tmpl new file mode 100644 index 0000000000..9df58758d4 --- /dev/null +++ b/templates/devtest/dropdown.tmpl @@ -0,0 +1,74 @@ +{{template "base/head" .}} + +
+

Dropdown

+ a.k.a. overflow menu, ellipsis menu + +
+ + + + + +
+ +
+ +{{template "base/footer" .}} diff --git a/templates/explore/search.tmpl b/templates/explore/search.tmpl index 446d23f253..e7e6b740c4 100644 --- a/templates/explore/search.tmpl +++ b/templates/explore/search.tmpl @@ -11,7 +11,7 @@ {{end}} diff --git a/tests/e2e/actions.test.e2e.ts b/tests/e2e/actions.test.e2e.ts index 4a1b8a3ec2..2f5d09e561 100644 --- a/tests/e2e/actions.test.e2e.ts +++ b/tests/e2e/actions.test.e2e.ts @@ -70,6 +70,53 @@ test.describe('Workflow Authenticated user2', () => { test('dispatch success', async ({page}) => { await dispatchSuccess(page); }); + + test('Disable/enable workflow', async ({page}) => { + await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml'); + + const menuOpener = page.locator('.filter.menu details.dropdown > summary'); + const disableButton = page.locator('a[data-url^="/user2/test_workflows/actions/disable"]'); + const enableButton = page.locator('a[data-url^="/user2/test_workflows/actions/enable"]'); + const disabledLabel = page.locator('.vertical.menu .item.active .ui.label').getByText('Disabled'); + const flashBanner = page.locator('#flash-message'); + + // Overflow menu is hidden + await expect(disableButton).toBeHidden(); + await expect(enableButton).toBeHidden(); + + await menuOpener.click(); + + // The current "Enabled" state is what previous tests left, but this test is built to not care + if (await disableButton.isVisible()) { + // Assert elemeents on page + await expect(enableButton).toBeHidden(); + await expect(disabledLabel).toBeHidden(); + + // Flip the state + await disableButton.click(); + await flashBanner.waitFor(); + await menuOpener.click(); + + // Assert elemeents on page + await expect(enableButton).toBeVisible(); + await expect(disableButton).toBeHidden(); + await expect(disabledLabel).toBeVisible(); + } else { + // Assert elemeents on page + await expect(enableButton).toBeVisible(); + await expect(disabledLabel).toBeVisible(); + + // Flip the state + await enableButton.click(); + await flashBanner.waitFor(); + await menuOpener.click(); + + // Assert elemeents on page + await expect(enableButton).toBeHidden(); + await expect(disableButton).toBeVisible(); + await expect(disabledLabel).toBeHidden(); + } + }); }); test('workflow dispatch box not available for unauthenticated users', async ({page}) => { diff --git a/tests/e2e/dropdown.test.e2e.ts b/tests/e2e/dropdown.test.e2e.ts index c4098e102a..e90ea93d6b 100644 --- a/tests/e2e/dropdown.test.e2e.ts +++ b/tests/e2e/dropdown.test.e2e.ts @@ -5,6 +5,7 @@ // templates/shared/user/actions_menu.tmpl // templates/org/header.tmpl // templates/explore/search.tmpl +// templates/devtest/dropdown.tmpl // web_src/js/modules/dropdown.ts // @watch end @@ -226,4 +227,33 @@ test.describe(`Visual properties`, () => { expect(await activeItem.evaluate((el) => getComputedStyle(el).backgroundColor)).toBe('rgb(226, 226, 229)'); expect(await inactiveItem.evaluate((el) => getComputedStyle(el).backgroundColor)).toBe('rgba(0, 0, 0, 0)'); }); + + test('Devtest', async ({browser}) => { + const context = await browser.newContext({javaScriptEnabled: false}); + const page = await context.newPage(); + + // `/devtest` has dropdowns with various combinations of items + await page.goto('/devtest/dropdown'); + + // Dropdown with just 3 items and nothing special + await page.locator(`#dropdown-1 > summary`).click(); + expect(await page.locator(`#dd1_g1_i1`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('4px 4px 0px 0px'); + expect(await page.locator(`#dd1_g1_i2`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px'); + expect(await page.locator(`#dd1_g1_i3`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px 0px 4px 4px'); + await page.keyboard.press('Enter'); // Exit dropdown - page is in noJS mode + + // Dropdown with two groups of items separated with an
+ await page.locator(`#dropdown-2 > summary`).click(); + expect(await page.locator(`#dd2_g1_i1`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('4px 4px 0px 0px'); + expect(await page.locator(`#dd2_g1_i2`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px'); + expect(await page.locator(`#dd2_g1_i3`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px'); + expect(await page.locator(`#dd2_g2_i1`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px'); + expect(await page.locator(`#dd2_g2_i2`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px'); + expect(await page.locator(`#dd2_g2_i3`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('0px 0px 4px 4px'); + await page.keyboard.press('Enter'); // Exit dropdown - page is in noJS mode + + // Dropdown with only one item, which should be completely round + await page.locator(`#dropdown-3 > summary`).click(); + expect(await page.locator(`#dd3_g1_i1`).evaluate((el) => getComputedStyle(el).borderRadius)).toBe('4px'); + }); }); diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index d7cc1dff87..bd294a132f 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -107,7 +107,7 @@ func TestE2e(t *testing.T) { defer test.MockVariableValue(&setting.Quota.Enabled, true)() defer test.MockVariableValue(&testE2eWebRoutes, routers.NormalRoutes())() } - if testname == "buttons.test.e2e" { + if testname == "buttons.test.e2e" || testname == "dropdown.test.e2e" { defer test.MockVariableValue(&setting.IsProd, false)() defer test.MockVariableValue(&testE2eWebRoutes, routers.NormalRoutes())() } diff --git a/web_src/css/modules/dropdown.css b/web_src/css/modules/dropdown.css index dc7e81ed7b..39a8f21b58 100644 --- a/web_src/css/modules/dropdown.css +++ b/web_src/css/modules/dropdown.css @@ -107,6 +107,9 @@ details.dropdown > .content > ul { &:last-of-type > li:last-child { border-radius: 0 0 var(--border-radius) var(--border-radius); } + &:only-of-type > li:only-child { + border-radius: var(--border-radius); + } } /* General styling of list items */