diff --git a/convert_icons_report.md b/convert_icons_report.md new file mode 100644 index 0000000000..9b6ae893eb --- /dev/null +++ b/convert_icons_report.md @@ -0,0 +1,221 @@ +# Font Awesome → Material Symbols icon migration — report + +## Goal + +Migrate o-spreadsheet's legacy Font Awesome icons (`class="fa fa-xxx"`) to the new +Odoo UI Icon (`oi`) system backed by **Material Symbols**, using the `data-icon` +attribute — e.g. `class="fa fa-check"` → `class="oi" data-icon="check"`. + +## Source of truth + +The exact `fa-* → material-symbol` mapping was **not** invented. It was taken from the +authoritative Odoo webclient stylesheet: + +``` +/workspace/odoo/community/addons/web/static/src/webclient/icons.scss +``` + +This is the temporary `fa-*` compatibility mapping referenced in the migration tip +(the file that lets `fa-*` classes keep working during the transition). Each value +below is the `icon-content(...)` that Odoo maps the corresponding `fa-*` class to. + +## Key architectural decision: fonts / fill + +Odoo renders `oi`/`fa` icons with its **own** in-house fonts +(`material_symbols_outlined` + `odoo_ui_icons`), where _filled_ variants are encoded +as a separate `_f` ligature (e.g. `settings_f`) and brand glyphs live in a custom font. + +o-spreadsheet is a **standalone** component and this branch instead vendored the public +[`material-symbols`](https://www.npmjs.com/package/material-symbols) npm package +(already added to `package.json` + linked in `demo/index.html`). That package ships the +**variable** font exposing a `FILL` axis. So the one necessary adaptation vs. Odoo: + +- **Filled icons** use `font-variation-settings: "FILL" 1` (driven by an `--oi-mi-fill` + CSS variable + the `oi-filled` class) instead of Odoo's `_f` ligature suffix. +- All non-filled `data-icon` **values are identical** to Odoo's mapping, so the markup + stays consistent whether rendered standalone or embedded in Odoo. + +One value had to deviate because of the npm package version (`0.44.12`): + +- `fa-thumb-tack` → Odoo maps to `push_pin`, which is **absent** from + `material-symbols@0.44.12`. Used `keep` instead — the current Material Symbols name + for the same pushpin glyph (verified present in the package's `index.d.ts`). + +Every other chosen `data-icon` value was verified to exist in +`node_modules/material-symbols/index.d.ts`. + +## CSS infrastructure — `src/components/icons/icons.css` + +Added an `.oi` base class modeled on Odoo's `material-icon-base` +(`web/static/src/webclient/icons.scss`), adapted for the variable font: + +- `.oi` — `font-family: "Material Symbols Outlined"`, `::before { content: attr(data-icon) }`, + `font-variation-settings: "FILL" var(--oi-mi-fill, 0)`, plus the `::before` + `transform: scale(1.214285)` spacing compensation Odoo uses so glyphs fill the box + like FA did. +- `.oi-outlined` / `.oi-filled` — toggle `--oi-mi-fill` between `0` and `1`. +- `.oi-rotate-90/180/270` — rotation helpers (replace FontAwesome's `fa-rotate-*`). +- `.oi-stack` / `.oi-stack-1x` / `.oi-stack-2x` — icon stacking (mirrors Odoo's kept-for- + compat stack classes), used by the composer assistant overlay. +- Renamed the o-spreadsheet-local `.fa-small` helper to `.oi-small`. + +`fa-inverse` does not exist in Odoo's icons.scss; the composer's "inverse" background disc +is now handled locally in `composer.css` (see below). + +## Full mapping applied + +| Font Awesome class | data-icon | fill | +| ----------------------------- | --------------------------- | ---- | +| fa-clipboard | assignment | | +| fa-clone | content_copy | | +| fa-eye | visibility | | +| fa-eye-slash | visibility_off | | +| fa-bar-chart | bar_chart | | +| fa-file-image-o | image | | +| fa-link | link | | +| fa-check-square-o | check_box | | +| fa-align-center (+rotate-270) | format_align_center | | +| fa-pencil-square-o | edit_square | | +| fa-external-link | open_in_new | | +| fa-chain-broken | link_off | | +| fa-caret-up | arrow_drop_up | | +| fa-caret-down | arrow_drop_down | | +| fa-caret-right | arrow_right | | +| fa-caret-left | arrow_left | | +| fa-trash-o | delete | | +| fa-trash | delete | ✓ | +| fa-refresh | refresh | | +| fa-exchange | swap_horiz | | +| fa-sort-alpha-asc | sort_by_alpha | | +| fa-sort-alpha-desc | sort_by_alpha | | +| fa-magic | wand_stars | | +| fa-filter | filter_alt | ✓ | +| fa-search | search | | +| fa-search-plus | zoom_in | | +| fa-exclamation-triangle | warning | | +| fa-exclamation-circle | error | | +| fa-download | download | | +| fa-moon-o | dark_mode | | +| fa-thumb-tack | keep (Odoo: push_pin) | | +| fa-lock | lock | | +| fa-unlock | lock_open | | +| fa-print | print | | +| fa-cog | settings | ✓ | +| fa-angle-left | keyboard_arrow_left | | +| fa-angle-down | keyboard_arrow_down | | +| fa-angle-double-right | keyboard_double_arrow_right | | +| fa-angle-double-left | keyboard_double_arrow_left | | +| fa-circle | circle | ✓ | +| fa-question-circle | help | | +| fa-times-circle | cancel | ✓ | +| fa-times | close | | +| fa-ellipsis-v | more_vert | | +| fa-compress | close_fullscreen | | +| fa-expand | expand_content | | +| fa-sliders | tune | | +| fa-paint-brush | design_services | | +| fa-undo | undo | | + +## Files changed + +### Source (`src/`) + +- `components/icons/icons.css` — new `.oi` infrastructure (see above). +- `components/icons/icons.xml` — 33 icon templates migrated (incl. `IRREGULARITY_MAP` + using `oi-rotate-270`, filled `TRASH_FILLED`, `oi-small` wrappers). +- `components/composer/composer/composer.xml` + `composer.css` — assistant help/close + overlays migrated to `oi-stack` + `data-icon` (`help` / `cancel` / background `circle`); + CSS selectors switched from `.fa-question-circle`/`.fa-times-circle`/`.fa-stack` to + `[data-icon=...]`/`.oi-stack`; added `.o-composer-assistant-icon-bg` to colour the + background disc (replacing `fa-inverse`). +- `components/figures/figure_carousel/figure_carousel.xml` — angle-down, full-screen + toggle (`t-att-data-icon`), ellipsis menu button. +- `components/figures/chart/chart_dashboard_menu/chart_dashboard_menu.{ts,xml}` — + full-screen menu item now carries an `icon` field bound via `t-att-data-icon`; + ellipsis button migrated. +- `components/side_panel/...` — pivot measure (dynamic show/hide eye via + `t-att-data-icon`), pivot dimension/custom-groups delete, layout configurator warning, + pivot & chart side-panel config/design icons, defer-update undo, cog-wheel menu. +- `components/top_bar/top_bar.xml`, `link/link_editor/link_editor.xml`, + `small_bottom_bar/*`, `side_panel/perf_profile/perf_profile_panel.xml`, + `side_panel/side_panel/side_panel.xml`. + +### Demo / packaging + +- `demo/index.html`, `demo/minimalist.html` — dropped the `font-awesome` stylesheet, + kept/added the `material-symbols` stylesheet. +- `package.json` — removed the `font-awesome` dependency (Material Symbols now the only + icon font). NOTE: run `npm install` to prune `font-awesome` from `package-lock.json`. + +### Tests + +- ~15 test files: `.fa-X` selectors → `[data-icon='Y']`. +- `tests/figures/carousel/carousel_full_screen.test.ts` — `toHaveClass("fa-expand")` + → `toHaveAttribute("data-icon", "expand_content")` (and compress → close_fullscreen). +- `tests/figures/chart/chart_menu_dashboard_component.test.ts` — the + `extendMockGetBoundingClientRect` helper matches by **class name** (`classList.contains`), + and the ellipsis button no longer has a `fa-ellipsis-v` class; re-keyed the mock to the + still-present `o-chart-dashboard-item` class and updated the click selector to + `[data-icon='more_vert']`. +- Snapshots regenerated with `jest -u` (14 snapshots across the affected suites). + +## Verification + +- `rtk grep` confirms **no** `fa fa-*` / `fa-stack` / `fa-small` / `fa-inverse` class + usages remain in `src/`, `tests/` (source or snapshots), or `demo/`. +- TypeScript type-check (`tests/tsconfig.json`): **passes** (validates the new + `MenuItem.icon` field). +- Full jest suite: **286 suites / 15189 tests passing**, 185 snapshots + (24 regenerated by this migration, all others unchanged). + +## Environment note (not part of the change) + +`node_modules` was populated for macOS; on this linux/arm64 box the native bindings were +the wrong arch. Had to install `@swc/core-linux-arm64-gnu` / +`@unrs/resolver-binding-linux-arm64-gnu` and reinstall `canvas` for tests to run. This is +a local environment fix only — no repo files affected. + +## Appendix: the two strategies (original vs Odoo) and what shipped + +The final implementation is a **hybrid**, not a wholesale copy of Odoo's approach. + +### The "other" strategy (original, pre-Odoo) + +Before consulting the Odoo files, the plan was to derive everything from general +Material Symbols knowledge + the npm package: + +1. **Mapping** — invent the `fa-* → data-icon` values from memory, validating only that + the names _exist_ in `node_modules/material-symbols/index.d.ts`. There was no source + of truth for _which_ symbol Odoo actually chose, so several were wrong guesses: + - `fa-clipboard` → guessed `content_paste` · Odoo: **`assignment`** + - `fa-paint-brush` → guessed `brush`/`format_paint` · Odoo: **`design_services`** + - filled-ness was guesswork — no way to know `fa-cog`/`fa-filter`/`fa-circle`/ + `fa-times-circle`/`fa-trash` are meant to be **filled** variants. +2. **`.oi` base CSS** — hand-written from scratch. +3. **Fill** — render filled icons via the variable font's `FILL` axis + (`font-variation-settings: "FILL" 1`, driven by `--oi-mi-fill` + `oi-filled`). + +### Odoo's strategy (`web/static/src/webclient/icons.scss`) + +1. **Mapping** — the authoritative `fa-* → material-symbol` table (the temporary + compatibility mapping referenced by the migration tip). +2. **`.oi` base** — the `material-icon-base` mixin (`content: attr(data-icon)`, + `::before { transform: scale(1.214285) }` spacing compensation, `vertical-align: -11.5%`). +3. **Fill** — **not** the FILL axis: Odoo appends a `_f` **ligature suffix** + (`content: attr(data-icon) "_f"`), because it ships its own in-house fonts + (`material_symbols_outlined` + `odoo_ui_icons`) where filled glyphs and brand icons + are baked in as separate ligatures. + +### What actually shipped (hybrid) + +- ✅ Adopted Odoo's **mapping values** (the corrected part). +- ✅ Modeled the **`.oi` base CSS** on Odoo's `material-icon-base`. +- ❌ Did **not** adopt Odoo's `_f` fill mechanism — kept the **original FILL-axis** + approach. + +The fill strategy is the deliberate divergence: o-spreadsheet (standalone) vendors the +public npm `material-symbols` **variable font**, which exposes a `FILL` axis but has no +`_f` ligatures or brand glyphs. Odoo's `_f` scheme would not render against that font. +For the same reason, one mapping value also differs by necessity: `fa-thumb-tack` → +Odoo's `push_pin` is absent from `material-symbols@0.44.12`, so `keep` (the same pushpin +glyph under its current name) is used instead. diff --git a/demo/index.html b/demo/index.html index d36427e93c..9b2cad4f81 100644 --- a/demo/index.html +++ b/demo/index.html @@ -9,7 +9,7 @@ - + O-spreadsheet Dev diff --git a/demo/minimalist.html b/demo/minimalist.html index 22acc6f821..3a155954ac 100644 --- a/demo/minimalist.html +++ b/demo/minimalist.html @@ -5,8 +5,8 @@ - - + + O-spreadsheet Minimal diff --git a/package-lock.json b/package-lock.json index ff632b49eb..ea24f91c10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@odoo/owl": "3.0.0-alpha.34", "bootstrap": "5.3.3", - "font-awesome": "^4.7.0", + "material-symbols": "^0.44.12", "rbush": "^3.0.1" }, "devDependencies": { @@ -6461,13 +6461,6 @@ } } }, - "node_modules/font-awesome": { - "version": "4.7.0", - "license": "(OFL-1.1 AND MIT)", - "engines": { - "node": ">=0.10.3" - } - }, "node_modules/for-in": { "version": "1.0.2", "dev": true, @@ -10941,6 +10934,12 @@ "node": ">=0.10.0" } }, + "node_modules/material-symbols": { + "version": "0.44.12", + "resolved": "https://registry.npmjs.org/material-symbols/-/material-symbols-0.44.12.tgz", + "integrity": "sha512-lZkS9BwISNU2Jbup+IlwZrr9z1oAK6zx38qPpe7mNzkXwiIEnQaxPz1Sf3bNZ0MX15LNlJQNjKI3MWLgmo83+Q==", + "license": "Apache-2.0" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", diff --git a/package.json b/package.json index 7a022f674b..794bd8ab5f 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "dependencies": { "@odoo/owl": "3.0.0-alpha.34", "bootstrap": "5.3.3", - "font-awesome": "^4.7.0", + "material-symbols": "^0.44.12", "rbush": "^3.0.1" }, "jest": { diff --git a/src/components/composer/composer/composer.css b/src/components/composer/composer/composer.css index fde994d94e..7adb4e0876 100644 --- a/src/components/composer/composer/composer.css +++ b/src/components/composer/composer/composer.css @@ -48,18 +48,22 @@ pointer-events: none; } - .fa-stack { + .oi-stack { /* reset stack size which is doubled by default */ width: 18px; height: 18px; line-height: 18px; } + .o-composer-assistant-icon-bg { + color: white; + } + .force-open-assistant { left: -1px; top: -1px; - .fa-question-circle { + [data-icon="help"] { color: var(--os-primary-button-bg); } } @@ -76,7 +80,7 @@ } .o-composer-assistant-container { - .fa-times-circle { + [data-icon="cancel"] { color: var(--os-primary-button-bg); } } diff --git a/src/components/composer/composer/composer.xml b/src/components/composer/composer/composer.xml index ff354181d0..e861f100e6 100644 --- a/src/components/composer/composer/composer.xml +++ b/src/components/composer/composer/composer.xml @@ -15,9 +15,9 @@ t-on-click.stop="this.openAssistant" t-on-pointerdown.prevent.stop="" t-on-pointerup.stop="" - class="fa-stack position-absolute translate-middle force-open-assistant fs-4"> - - + class="oi-stack position-absolute translate-middle force-open-assistant fs-4"> + +
- - + class="oi-stack position-absolute top-0 start-100 translate-middle fs-4"> + +
void; preview?: string; + icon?: string; } export class ChartDashboardMenu extends Component { @@ -75,7 +76,8 @@ export class ChartDashboardMenu extends Component { return { id: "fullScreenChart", label: isFullScreen ? _t("Exit Full Screen") : _t("Full Screen"), - class: `text-muted fa ${isFullScreen ? "fa-compress" : "fa-expand"}`, + class: "text-muted oi", + icon: isFullScreen ? "close_fullscreen" : "open_in_full", onClick: () => { this.fullScreenFigureStore.toggleFullScreenFigure(figureId); }, diff --git a/src/components/figures/chart/chart_dashboard_menu/chart_dashboard_menu.xml b/src/components/figures/chart/chart_dashboard_menu/chart_dashboard_menu.xml index 9595b91098..a8788380ee 100644 --- a/src/components/figures/chart/chart_dashboard_menu/chart_dashboard_menu.xml +++ b/src/components/figures/chart/chart_dashboard_menu/chart_dashboard_menu.xml @@ -15,6 +15,7 @@
@@ -22,7 +23,8 @@
diff --git a/src/components/figures/figure_carousel/figure_carousel.css b/src/components/figures/figure_carousel/figure_carousel.css index b92b2acd97..4cd7a1dfb7 100644 --- a/src/components/figures/figure_carousel/figure_carousel.css +++ b/src/components/figures/figure_carousel/figure_carousel.css @@ -37,17 +37,12 @@ .o-carousel-button { cursor: pointer; - &.o-carousel-tabs-dropdown { - font-size: 16px; - line-height: 16px; - } - &.o-carousel-full-screen-button { margin: 1px; } &.o-carousel-menu-button { - padding: 3px 5px; + padding: 1px 2px; } &.active, diff --git a/src/components/figures/figure_carousel/figure_carousel.xml b/src/components/figures/figure_carousel/figure_carousel.xml index a6162cc9bd..d9b9387b4e 100644 --- a/src/components/figures/figure_carousel/figure_carousel.xml +++ b/src/components/figures/figure_carousel/figure_carousel.xml @@ -31,11 +31,11 @@
diff --git a/tests/composer/autocomplete_dropdown_component.test.ts b/tests/composer/autocomplete_dropdown_component.test.ts index 7bda3b4a60..8d493710aa 100644 --- a/tests/composer/autocomplete_dropdown_component.test.ts +++ b/tests/composer/autocomplete_dropdown_component.test.ts @@ -348,7 +348,7 @@ describe("Functions autocomplete", () => { expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(1); // hide the auto-complete - await click(fixture, ".fa-times-circle"); + await click(fixture, "[data-icon='cancel']"); expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(0); // Enter should CONFIRM as-typed (no autocomplete) and stop edition @@ -358,7 +358,7 @@ describe("Functions autocomplete", () => { // show it again await typeInComposer("=SU"); - await click(fixture, ".fa-question-circle"); + await click(fixture, "[data-icon='help']"); expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(1); await keyDown({ key: "Enter" }); expect(composerStore.currentContent).toBe("=SUM("); @@ -367,7 +367,7 @@ describe("Functions autocomplete", () => { test("after force-closing assistant, plain text in another cell still confirms", async () => { await typeInComposer("=SU"); expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(1); - await click(fixture, ".fa-times-circle"); + await click(fixture, "[data-icon='cancel']"); expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(0); await keyDown({ key: "Enter" }); @@ -451,7 +451,7 @@ describe("Data validation autocomplete", () => { }); await typeInComposer(""); expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(3); - expect(fixture.querySelector(".fa-times-circle")).toBeFalsy(); + expect(fixture.querySelector("[data-icon='cancel']")).toBeFalsy(); }); test("closing formula assistant should not affect data validation autocomplete visibility", async () => { @@ -463,18 +463,18 @@ describe("Data validation autocomplete", () => { await typeInComposer("=SU"); expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(1); - expect(fixture.querySelector(".fa-times-circle")).toBeTruthy(); + expect(fixture.querySelector("[data-icon='cancel']")).toBeTruthy(); - await click(fixture, ".fa-times-circle"); + await click(fixture, "[data-icon='cancel']"); expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(0); - expect(fixture.querySelector(".fa-times-circle")).toBeFalsy(); + expect(fixture.querySelector("[data-icon='cancel']")).toBeFalsy(); await keyDown({ key: "Escape" }); await typeInComposer(""); expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(3); - expect(fixture.querySelector(".fa-times-circle")).toBeFalsy(); - expect(fixture.querySelector(".fa-question-circle")).toBeFalsy(); + expect(fixture.querySelector("[data-icon='cancel']")).toBeFalsy(); + expect(fixture.querySelector("[data-icon='help']")).toBeFalsy(); }); test("after force-closing formula assistant, Enter in data validation still selects from dropdown", async () => { @@ -485,9 +485,9 @@ describe("Data validation autocomplete", () => { }); await typeInComposer("=SU"); - await click(fixture, ".fa-times-circle"); + await click(fixture, "[data-icon='cancel']"); expect(fixture.querySelectorAll(".o-autocomplete-value")).toHaveLength(0); - expect(fixture.querySelector(".fa-times-circle")).toBeFalsy(); + expect(fixture.querySelector("[data-icon='cancel']")).toBeFalsy(); await keyDown({ key: "Escape" }); await typeInComposer(""); diff --git a/tests/composer/composer_integration_component.test.ts b/tests/composer/composer_integration_component.test.ts index 83f79a49af..32839dc477 100644 --- a/tests/composer/composer_integration_component.test.ts +++ b/tests/composer/composer_integration_component.test.ts @@ -968,12 +968,12 @@ describe("TopBar composer", () => { composerEl = document.querySelector(".o-spreadsheet-topbar .o-composer"); expect(document.activeElement).toBe(composerEl); expect(fixture.querySelector(".o-formula-assistant")).toBeDefined(); - expect(fixture.querySelector(".o-spreadsheet-topbar .fa-question-circle")).toBe(null); - expect(fixture.querySelector(".o-spreadsheet-topbar .fa-times-circle")).not.toBe(null); - await simulateClick(".o-spreadsheet-topbar .fa-times-circle"); + expect(fixture.querySelector(".o-spreadsheet-topbar [data-icon='help']")).toBe(null); + expect(fixture.querySelector(".o-spreadsheet-topbar [data-icon='cancel']")).not.toBe(null); + await simulateClick(".o-spreadsheet-topbar [data-icon='cancel']"); expect(document.activeElement).toBe(composerEl); expect(fixture.querySelector(".o-formula-assistant")).toBe(null); - await simulateClick(".o-spreadsheet-topbar .fa-question-circle"); + await simulateClick(".o-spreadsheet-topbar [data-icon='help']"); expect(fixture.querySelector(".o-formula-assistant")).toBeDefined(); expect(document.activeElement).toBe(composerEl); }); diff --git a/tests/composer/formula_assistant_component.test.ts b/tests/composer/formula_assistant_component.test.ts index 231ce24e13..251668bfb6 100644 --- a/tests/composer/formula_assistant_component.test.ts +++ b/tests/composer/formula_assistant_component.test.ts @@ -284,10 +284,10 @@ describe("formula assistant", () => { await typeInComposer("=FUNC1("); expect(document.activeElement).toBe(composerEl); expect(fixture.querySelector(".o-formula-assistant")).toBeDefined(); - expect(fixture.querySelector(".fa-question-circle")).toBe(null); - await click(fixture, ".fa-times-circle"); + expect(fixture.querySelector("[data-icon='help']")).toBe(null); + await click(fixture, "[data-icon='cancel']"); expect(fixture.querySelector(".o-formula-assistant")).toBe(null); - await click(fixture, ".fa-question-circle"); + await click(fixture, "[data-icon='help']"); expect(fixture.querySelector(".o-formula-assistant")).toBeDefined(); }); diff --git a/tests/conditional_formatting/__snapshots__/conditional_formatting_panel_component.test.ts.snap b/tests/conditional_formatting/__snapshots__/conditional_formatting_panel_component.test.ts.snap index 034ffcf4b4..3f32733845 100644 --- a/tests/conditional_formatting/__snapshots__/conditional_formatting_panel_component.test.ts.snap +++ b/tests/conditional_formatting/__snapshots__/conditional_formatting_panel_component.test.ts.snap @@ -82,7 +82,8 @@ exports[`UI of conditional formats Conditional formatting list panel simple snap class="o-icon" > @@ -164,7 +165,8 @@ exports[`UI of conditional formats Conditional formatting list panel simple snap class="o-icon" > diff --git a/tests/figures/carousel/carousel_full_screen.test.ts b/tests/figures/carousel/carousel_full_screen.test.ts index 2ae2b13869..e0c97a32f7 100644 --- a/tests/figures/carousel/carousel_full_screen.test.ts +++ b/tests/figures/carousel/carousel_full_screen.test.ts @@ -34,11 +34,14 @@ describe("full screen carousel", () => { addNewChartToCarousel(model, "carouselId"); model.updateMode("dashboard"); await nextTick(); - expect(".o-carousel-full-screen-button").toHaveClass("fa-expand"); + expect(".o-carousel-full-screen-button").toHaveAttribute("data-icon", "expand_content"); await click(fixture, ".o-figure .o-carousel-full-screen-button"); expect(".o-fullscreen-figure").toHaveCount(1); - expect(".o-fullscreen-figure .o-carousel-full-screen-button").toHaveClass("fa-compress"); + expect(".o-fullscreen-figure .o-carousel-full-screen-button").toHaveAttribute( + "data-icon", + "close_fullscreen" + ); await click(fixture, ".o-fullscreen-figure .o-carousel-full-screen-button"); expect(".o-fullscreen-figure").toHaveCount(0); diff --git a/tests/figures/chart/chart_full_screen.test.ts b/tests/figures/chart/chart_full_screen.test.ts index a73d58ecb6..1046a594a3 100644 --- a/tests/figures/chart/chart_full_screen.test.ts +++ b/tests/figures/chart/chart_full_screen.test.ts @@ -41,12 +41,12 @@ describe("chart menu for dashboard", () => { model.updateMode("dashboard"); await nextTick(); - expect(".o-figure .fa-expand").toHaveCount(1); + expect(".o-figure [data-icon='expand_content']").toHaveCount(1); expect(".o-fullscreen-figure").toHaveCount(0); - await click(fixture, ".o-figure .fa-expand"); + await click(fixture, ".o-figure [data-icon='expand_content']"); expect(".o-fullscreen-figure").toHaveCount(1); - expect(".o-figure .fa-compress").toHaveCount(2); // One in the original chart, one in the full screen overlay + expect(".o-figure [data-icon='close_fullscreen']").toHaveCount(2); // One in the original chart, one in the full screen overlay }); test("Cannot make scorecard chart fullscreen ", async () => { diff --git a/tests/figures/chart/chart_menu_dashboard_component.test.ts b/tests/figures/chart/chart_menu_dashboard_component.test.ts index 49a1c827c9..d7b71ebe9b 100644 --- a/tests/figures/chart/chart_menu_dashboard_component.test.ts +++ b/tests/figures/chart/chart_menu_dashboard_component.test.ts @@ -36,7 +36,7 @@ describe("chart menu for dashboard", () => { test("Can open menu to copy/download chart in dashboard mode", async () => { extendMockGetBoundingClientRect({ - "fa-ellipsis-v": () => ({ x: 100, y: 100, width: 20, height: 20 }), + "o-chart-dashboard-item": () => ({ x: 100, y: 100, width: 20, height: 20 }), }); createChart(model, { type: "bar" }, chartId); model.updateMode("dashboard"); @@ -46,7 +46,7 @@ describe("chart menu for dashboard", () => { await nextTick(); expect(".o-menu-item").toHaveCount(0); - await click(fixture, ".o-figure .fa-ellipsis-v"); + await click(fixture, ".o-figure [data-icon='more_vert']"); expect(getElStyle(".o-popover", "top")).toBe("100px"); expect(getElStyle(".o-popover", "left")).toBe("120px"); const menuItems = [...document.querySelectorAll(".o-menu-item")].map( diff --git a/tests/link/__snapshots__/link_display_component.test.ts.snap b/tests/link/__snapshots__/link_display_component.test.ts.snap index 1caa357cad..3e85e5f325 100644 --- a/tests/link/__snapshots__/link_display_component.test.ts.snap +++ b/tests/link/__snapshots__/link_display_component.test.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`link display component simple snapshot 1`] = `""`; +exports[`link display component simple snapshot 1`] = `""`; diff --git a/tests/menus/__snapshots__/context_menu_component.test.ts.snap b/tests/menus/__snapshots__/context_menu_component.test.ts.snap index f8ec47303e..c3962a7471 100644 --- a/tests/menus/__snapshots__/context_menu_component.test.ts.snap +++ b/tests/menus/__snapshots__/context_menu_component.test.ts.snap @@ -61,7 +61,8 @@ exports[`Context MenuPopover integration tests context menu simple rendering 1`] class="o-icon" > @@ -155,10 +156,11 @@ exports[`Context MenuPopover integration tests context menu simple rendering 1`] >
@@ -274,10 +276,11 @@ exports[`Context MenuPopover integration tests context menu simple rendering 1`] >
@@ -306,7 +309,8 @@ exports[`Context MenuPopover integration tests context menu simple rendering 1`] class="o-icon" > @@ -337,7 +341,8 @@ exports[`Context MenuPopover integration tests context menu simple rendering 1`] class="o-icon" > @@ -368,7 +373,8 @@ exports[`Context MenuPopover integration tests context menu simple rendering 1`] class="o-icon" > @@ -384,10 +390,11 @@ exports[`Context MenuPopover integration tests context menu simple rendering 1`] >
@@ -416,7 +423,8 @@ exports[`Context MenuPopover integration tests context menu simple rendering 1`] class="o-icon" > diff --git a/tests/menus/context_menu_component.test.ts b/tests/menus/context_menu_component.test.ts index 733b293303..21ae8825b7 100644 --- a/tests/menus/context_menu_component.test.ts +++ b/tests/menus/context_menu_component.test.ts @@ -707,11 +707,11 @@ describe("Context MenuPopover internal tests", () => { }, ]); await renderContextMenu(300, 300, { menuItems }); - expect(fixture.querySelector(".fa-search")).not.toBeNull(); + expect(fixture.querySelector("[data-icon='search']")).not.toBeNull(); visible = false; parent.render(true); await nextTick(); - expect(fixture.querySelector(".fa-search")).toBeNull(); + expect(fixture.querySelector("[data-icon='search']")).toBeNull(); }); test("Can change icon color", async () => { diff --git a/tests/menus/menu_component.test.ts b/tests/menus/menu_component.test.ts index 879b618832..d792738c38 100644 --- a/tests/menus/menu_component.test.ts +++ b/tests/menus/menu_component.test.ts @@ -84,7 +84,7 @@ describe("Menu component", () => { }); const rootItem = fixture.querySelector(".o-menu div[data-name='root']")!; - expect(rootItem.querySelector(".fa-search")).not.toBeNull(); - expect(rootItem.querySelector(".fa-caret-right")).not.toBeNull(); + expect(rootItem.querySelector("[data-icon='search']")).not.toBeNull(); + expect(rootItem.querySelector("[data-icon='arrow_right']")).not.toBeNull(); }); }); diff --git a/tests/named_ranges/named_ranges_selector_component.test.ts b/tests/named_ranges/named_ranges_selector_component.test.ts index 876aeff6f3..45251ebe7f 100644 --- a/tests/named_ranges/named_ranges_selector_component.test.ts +++ b/tests/named_ranges/named_ranges_selector_component.test.ts @@ -104,7 +104,7 @@ describe("Named ranges topbar selector", () => { createNamedRange(model, "AnotherRange", "C3:D4", "sh2"); await mountRangeSelector(); - await simulateClick(".o-named-range-selector .fa-caret-down"); + await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']"); const menuItems = [...document.querySelectorAll(".o-menu-item")]; const getMenuItemText = (item: HTMLElement) => { const name = item.querySelector(".o-menu-item-name")?.textContent?.trim() || ""; @@ -129,7 +129,7 @@ describe("Named ranges topbar selector", () => { const viewportWidth = viewport.right - viewport.left; const viewportHeight = viewport.bottom - viewport.top; - await simulateClick(".o-named-range-selector .fa-caret-down"); + await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']"); await simulateClick(".o-menu-item"); expect(model.getters.getSelectedZone()).toEqual(toZone("Y60:Z70")); @@ -145,7 +145,7 @@ describe("Named ranges topbar selector", () => { createNamedRange(model, "MyRange", "A1:B3"); await mountRangeSelector(); - await simulateClick(".o-named-range-selector .fa-caret-down"); + await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']"); triggerMouseEvent(".o-menu-item", "mouseenter"); await nextTick(); @@ -161,7 +161,7 @@ describe("Named ranges topbar selector", () => { test("Can open the named range side panel from the dropdown", async () => { await mountRangeSelector(); - await simulateClick(".o-named-range-selector .fa-caret-down"); + await simulateClick(".o-named-range-selector [data-icon='arrow_drop_down']"); expect(".o-menu-item").toHaveCount(1); await simulateClick(".o-menu-item"); diff --git a/tests/pivots/pivot_custom_groups/pivot_custom_groups_component.test.ts b/tests/pivots/pivot_custom_groups/pivot_custom_groups_component.test.ts index 096f9b99be..b115ffcc1a 100644 --- a/tests/pivots/pivot_custom_groups/pivot_custom_groups_component.test.ts +++ b/tests/pivots/pivot_custom_groups/pivot_custom_groups_component.test.ts @@ -50,7 +50,7 @@ describe("Pivot custom field panel", () => { customFields: { CustomField: TEST_CUSTOM_FIELD }, }); await openPivotSidePanel(); - await click(fixture, ".o-pivot-custom-group .fa-trash"); + await click(fixture, ".o-pivot-custom-group [data-icon='delete']"); const definition = model.getters.getPivotCoreDefinition(pivotId); expect(definition.customFields?.CustomField.groups).toEqual([]); }); diff --git a/tests/pivots/pivot_side_panel.test.ts b/tests/pivots/pivot_side_panel.test.ts index 448ac87748..8396d60698 100644 --- a/tests/pivots/pivot_side_panel.test.ts +++ b/tests/pivots/pivot_side_panel.test.ts @@ -187,7 +187,7 @@ describe("Pivot side panel", () => { await nextTick(); const customerDimEl = fixture.querySelectorAll(".pivot-dimension")[0]; - await click(customerDimEl, ".fa-trash"); + await click(customerDimEl, "[data-icon='delete']"); let definition = model.getters.getPivotCoreDefinition("1"); expect(definition.columns).toHaveLength(1); @@ -195,7 +195,7 @@ describe("Pivot side panel", () => { expect(definition.collapsedDomains?.ROW).toHaveLength(1); const clientDimEl = fixture.querySelectorAll(".pivot-dimension")[1]; - await click(clientDimEl, ".fa-trash"); + await click(clientDimEl, "[data-icon='delete']"); definition = model.getters.getPivotCoreDefinition("1"); expect(definition.rows).toHaveLength(1); expect(definition.collapsedDomains?.COL).toHaveLength(0); @@ -260,7 +260,7 @@ describe("Pivot side panel", () => { await nextTick(); const yearDimensionEl = fixture.querySelectorAll(".pivot-dimension")[1]; - await click(yearDimensionEl, ".fa-trash"); + await click(yearDimensionEl, "[data-icon='delete']"); const definition = model.getters.getPivotCoreDefinition("1"); expect(definition.columns).toHaveLength(1); diff --git a/tests/pivots/spreadsheet_pivot/__snapshots__/spreadsheet_pivot_side_panel.test.ts.snap b/tests/pivots/spreadsheet_pivot/__snapshots__/spreadsheet_pivot_side_panel.test.ts.snap index c658e24f7a..0368a4501b 100644 --- a/tests/pivots/spreadsheet_pivot/__snapshots__/spreadsheet_pivot_side_panel.test.ts.snap +++ b/tests/pivots/spreadsheet_pivot/__snapshots__/spreadsheet_pivot_side_panel.test.ts.snap @@ -11,7 +11,8 @@ exports[`Spreadsheet pivot side panel It should correctly be displayed 1`] = ` class="o-collapse-panel o-sidePanelAction rounded" > @@ -78,7 +79,8 @@ exports[`Spreadsheet pivot side panel It should correctly be displayed 1`] = ` class="o-sidePanel-tab o-panel-configuration d-flew flex-grow-1 text-center" > Configuration @@ -86,7 +88,8 @@ exports[`Spreadsheet pivot side panel It should correctly be displayed 1`] = ` class="o-sidePanel-tab o-panel-design d-flew flex-grow-1 text-center inactive" > Design @@ -117,7 +120,8 @@ exports[`Spreadsheet pivot side panel It should correctly be displayed 1`] = ` > Name @@ -972,10 +976,11 @@ exports[`Spreadsheet pivot side panel It should correctly be displayed 1`] = ` class="o-table-style-picker-arrow d-flex align-items-center px-1 border-start" >
@@ -1006,7 +1011,8 @@ exports[`Spreadsheet pivot side panel It should display only the selection input class="o-collapse-panel o-sidePanelAction rounded" > @@ -1073,7 +1079,8 @@ exports[`Spreadsheet pivot side panel It should display only the selection input class="o-sidePanel-tab o-panel-configuration d-flew flex-grow-1 text-center" > Configuration @@ -1081,7 +1088,8 @@ exports[`Spreadsheet pivot side panel It should display only the selection input class="o-sidePanel-tab o-panel-design d-flew flex-grow-1 text-center inactive" > Design @@ -1112,7 +1120,8 @@ exports[`Spreadsheet pivot side panel It should display only the selection input > Name @@ -1168,7 +1177,8 @@ exports[`Spreadsheet pivot side panel It should display only the selection input class="o-icon" > @@ -1932,10 +1942,11 @@ exports[`Spreadsheet pivot side panel It should display only the selection input class="o-table-style-picker-arrow d-flex align-items-center px-1 border-start" >
diff --git a/tests/pivots/spreadsheet_pivot/pivot_filter/pivot_filter_component.test.ts b/tests/pivots/spreadsheet_pivot/pivot_filter/pivot_filter_component.test.ts index c0c111a230..eefcbcef5e 100644 --- a/tests/pivots/spreadsheet_pivot/pivot_filter/pivot_filter_component.test.ts +++ b/tests/pivots/spreadsheet_pivot/pivot_filter/pivot_filter_component.test.ts @@ -83,7 +83,7 @@ describe("Spreadsheet pivot side panel", () => { const { model, fixture, env } = await setupPivotWithFilter(); env.openSidePanel("PivotSidePanel", { pivotId: "1" }); await nextTick(); - await click(fixture.querySelectorAll(".fa-trash")[2]); + await click(fixture.querySelectorAll("[data-icon='delete']")[2]); expect(model.getters.getPivotCoreDefinition("1").filters).toEqual([]); }); diff --git a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts index 6738da6b8e..1f52ae71b3 100644 --- a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts +++ b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts @@ -430,7 +430,7 @@ describe("Spreadsheet pivot side panel", () => { expect(fixture.querySelector(".o-popover")).toBeDefined(); await click(fixture.querySelectorAll(".o-autocomplete-value")[0]); expect(fixture.querySelectorAll(".pivot-dimension")).toHaveLength(1); - await click(fixture.querySelector(".fa-undo")!); + await click(fixture.querySelector("[data-icon='undo']")!); expect(fixture.querySelectorAll(".pivot-dimension")).toHaveLength(0); }); @@ -776,7 +776,7 @@ describe("Spreadsheet pivot side panel", () => { env.openSidePanel("PivotSidePanel", { pivotId: "2" }); await nextTick(); // update the pivot - await click(fixture.querySelector(".pivot-measure .fa-eye")!); + await click(fixture.querySelector(".pivot-measure [data-icon='visibility']")!); expect(mockNotify).toHaveBeenCalledTimes(0); }); @@ -797,11 +797,11 @@ describe("Spreadsheet pivot side panel", () => { const pivotDimensionEls = fixture.querySelectorAll(".pivot-dimension")!; const validDimensionEl = pivotDimensionEls[0]; expect(validDimensionEl.classList).not.toContain("pivot-dimension-invalid"); - expect(validDimensionEl.querySelector(".fa-exclamation-triangle")).toBe(null); + expect(validDimensionEl.querySelector("[data-icon='warning']")).toBe(null); const invalidDimensionEl = pivotDimensionEls[1]; expect(invalidDimensionEl.classList).toContain("pivot-dimension-invalid"); - expect(invalidDimensionEl.querySelector(".fa-exclamation-triangle")).not.toBe(null); + expect(invalidDimensionEl.querySelector("[data-icon='warning']")).not.toBe(null); }); test("Can update the name of a computed measure", async () => { @@ -853,11 +853,11 @@ describe("Spreadsheet pivot side panel", () => { ); env.openSidePanel("PivotSidePanel", { pivotId: "3" }); await nextTick(); - await click(fixture.querySelector(".pivot-measure .fa-eye")!); + await click(fixture.querySelector(".pivot-measure [data-icon='visibility']")!); expect(model.getters.getPivotCoreDefinition("3").measures).toEqual([ { id: "amount:sum", fieldName: "amount", aggregator: "sum", isHidden: true }, ]); - await click(fixture.querySelector(".pivot-measure .fa-eye-slash")!); + await click(fixture.querySelector(".pivot-measure [data-icon='visibility_off']")!); expect(model.getters.getPivotCoreDefinition("3").measures).toEqual([ { id: "amount:sum", fieldName: "amount", aggregator: "sum", isHidden: false }, ]); @@ -887,7 +887,7 @@ describe("Spreadsheet pivot side panel", () => { expect(model.getters.getPivotCoreDefinition("1").columns).toEqual([ { fieldName: "Amount", order: "desc" }, ]); - await clickAndDrag(".pivot-dimension .fa-trash", { x: 0, y: 30 }, undefined, true); + await clickAndDrag(".pivot-dimension [data-icon='delete']", { x: 0, y: 30 }, undefined, true); expect(model.getters.getPivotCoreDefinition("1").columns).toEqual([ { fieldName: "Amount", order: "desc" }, ]); @@ -947,14 +947,14 @@ describe("Spreadsheet pivot side panel", () => { test("Pivot sorting is removed when removing the sorted measure", async () => { expect(model.getters.getPivotCoreDefinition("2").sortedColumn).toEqual(sortedColumn); - await click(fixture, ".pivot-measure .fa-trash"); + await click(fixture, ".pivot-measure [data-icon='delete']"); expect(model.getters.getPivotCoreDefinition("2").sortedColumn).toBeUndefined(); }); test("Pivot sorting is removed when removing a column", async () => { expect(model.getters.getPivotCoreDefinition("2").sortedColumn).toEqual(sortedColumn); const column = fixture.querySelectorAll(".pivot-dimension")[0]; - await click(column, ".fa-trash"); + await click(column, "[data-icon='delete']"); expect(model.getters.getPivotCoreDefinition("2").sortedColumn).toBeUndefined(); }); diff --git a/tests/side_panels/building_blocks/__snapshots__/chart_title.test.ts.snap b/tests/side_panels/building_blocks/__snapshots__/chart_title.test.ts.snap index 7b6c658cca..1490337a13 100644 --- a/tests/side_panels/building_blocks/__snapshots__/chart_title.test.ts.snap +++ b/tests/side_panels/building_blocks/__snapshots__/chart_title.test.ts.snap @@ -95,10 +95,11 @@ exports[`Chart title Can render a chart title component 1`] = `
@@ -129,10 +130,11 @@ exports[`Chart title Can render a chart title component 1`] = `
diff --git a/tests/side_panels/building_blocks/__snapshots__/error_section.test.ts.snap b/tests/side_panels/building_blocks/__snapshots__/error_section.test.ts.snap index c98ed74ab4..69dbd495b5 100644 --- a/tests/side_panels/building_blocks/__snapshots__/error_section.test.ts.snap +++ b/tests/side_panels/building_blocks/__snapshots__/error_section.test.ts.snap @@ -14,10 +14,11 @@ exports[`Chart error section Can render a chart error section component 1`] = ` >
@@ -41,10 +42,11 @@ exports[`Chart error section Can render a chart error section component 1`] = ` >
diff --git a/tests/side_panels/perf_profile_panel.test.ts b/tests/side_panels/perf_profile_panel.test.ts index e2a462a6b4..2551df47f9 100644 --- a/tests/side_panels/perf_profile_panel.test.ts +++ b/tests/side_panels/perf_profile_panel.test.ts @@ -118,7 +118,7 @@ describe("PerfProfilePanel", () => { expect(".o-perf-entry-selected").toHaveCount(1); // Click re-analyze - await click(fixture, ".fa-refresh"); + await click(fixture, "[data-icon='refresh']"); // Selection is cleared expect(".o-perf-entry-selected").toHaveCount(0); diff --git a/tests/spreadsheet/__snapshots__/spreadsheet_component.test.ts.snap b/tests/spreadsheet/__snapshots__/spreadsheet_component.test.ts.snap index 484258615a..483bb3c6de 100644 --- a/tests/spreadsheet/__snapshots__/spreadsheet_component.test.ts.snap +++ b/tests/spreadsheet/__snapshots__/spreadsheet_component.test.ts.snap @@ -94,10 +94,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = ` class="o-hoverable-button" >
@@ -205,10 +206,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = `
@@ -288,10 +290,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = `
@@ -327,10 +330,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = `
@@ -555,10 +559,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = `
@@ -587,10 +592,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = `
@@ -619,10 +625,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = `
@@ -651,10 +658,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = `
@@ -694,10 +702,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = `
@@ -714,7 +723,8 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = ` class="o-icon filter-icon-active" > @@ -1100,10 +1110,11 @@ exports[`Simple Spreadsheet Component simple rendering snapshot 1`] = ` tabindex="-1" >
@@ -1171,10 +1182,11 @@ exports[`components take the small screen into account 1`] = ` class="o-hoverable-button" >
@@ -1282,10 +1294,11 @@ exports[`components take the small screen into account 1`] = `
@@ -1365,10 +1378,11 @@ exports[`components take the small screen into account 1`] = `
@@ -1404,10 +1418,11 @@ exports[`components take the small screen into account 1`] = `
@@ -1632,10 +1647,11 @@ exports[`components take the small screen into account 1`] = `
@@ -1664,10 +1680,11 @@ exports[`components take the small screen into account 1`] = `
@@ -1696,10 +1713,11 @@ exports[`components take the small screen into account 1`] = `
@@ -1728,10 +1746,11 @@ exports[`components take the small screen into account 1`] = `
@@ -1771,10 +1790,11 @@ exports[`components take the small screen into account 1`] = `
@@ -1791,7 +1811,8 @@ exports[`components take the small screen into account 1`] = ` class="o-icon filter-icon-active" > @@ -2085,7 +2106,8 @@ exports[`components take the small screen into account 1`] = ` class="py-1 px-1 mx-2 ribbon-toggler" > @@ -2206,10 +2228,11 @@ exports[`components take the small screen into account 1`] = ` tabindex="-1" >
diff --git a/tests/table/__snapshots__/filter_menu_component.test.ts.snap b/tests/table/__snapshots__/filter_menu_component.test.ts.snap index 5d52e540ed..cd01019ecb 100644 --- a/tests/table/__snapshots__/filter_menu_component.test.ts.snap +++ b/tests/table/__snapshots__/filter_menu_component.test.ts.snap @@ -146,7 +146,8 @@ exports[`Filter menu component Filter Tests Filter menu is correctly rendered 1` class="o-icon" > diff --git a/tests/test_helpers/pivot_helpers.ts b/tests/test_helpers/pivot_helpers.ts index 943983a711..fb467dff6f 100644 --- a/tests/test_helpers/pivot_helpers.ts +++ b/tests/test_helpers/pivot_helpers.ts @@ -75,10 +75,10 @@ export function removePivot(model: Model, pivotId: UID) { } export const SELECTORS = { - COG_WHEEL: ".fa-cog", - DUPLICATE_PIVOT: ".o-menu .fa-clone", - DELETE_PIVOT: ".o-menu .fa-trash-o", - FLIP_AXIS_PIVOT: ".o-menu .fa-exchange", + COG_WHEEL: "[data-icon='settings']", + DUPLICATE_PIVOT: ".o-menu [data-icon='content_copy']", + DELETE_PIVOT: ".o-menu [data-icon='delete']", + FLIP_AXIS_PIVOT: ".o-menu [data-icon='swap_horiz']", ZONE_INPUT: ".o-selection-input input", ZONE_CONFIRM: ".o-selection-input .o-selection-ok", };