diff --git a/addon/components/date-time-input.hbs b/addon/components/date-time-input.hbs index 79a04341..d2cf98c4 100644 --- a/addon/components/date-time-input.hbs +++ b/addon/components/date-time-input.hbs @@ -1,4 +1,4 @@ -
+
-
\ No newline at end of file +
diff --git a/addon/components/date-time-input.js b/addon/components/date-time-input.js index 3d99f7fd..b304ccc6 100644 --- a/addon/components/date-time-input.js +++ b/addon/components/date-time-input.js @@ -1,7 +1,7 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; -import { parse, format, isValid } from 'date-fns'; +import { parse, parseISO, format, isValid } from 'date-fns'; export default class DateTimeInputComponent extends Component { @tracked timeFormat = 'HH:mm'; @@ -13,10 +13,14 @@ export default class DateTimeInputComponent extends Component { constructor() { super(...arguments); - const value = this.parseValue(this.args.value); + this.syncValue(this.args.value); + } + + syncValue(value) { + const parsedValue = this.parseValue(value); - this.date = value ? format(value, this.dateFormat) : null; - this.time = value ? format(value, this.timeFormat) : null; + this.date = parsedValue ? format(parsedValue, this.dateFormat) : null; + this.time = parsedValue ? format(parsedValue, this.timeFormat) : null; } parseValue(value) { @@ -25,49 +29,81 @@ export default class DateTimeInputComponent extends Component { } if (typeof value === 'string') { - const parsedValue = parse(value, this.dateTimeFormat, new Date()); + const dateTimeValue = parse(value, this.dateTimeFormat, new Date()); + + if (isValid(dateTimeValue)) { + return dateTimeValue; + } + + const isoLocalValue = value.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})/); - if (isValid(parsedValue)) { - return parsedValue; + if (isoLocalValue) { + const localDateTimeValue = parse(`${isoLocalValue[1]} ${isoLocalValue[2]}`, this.dateTimeFormat, new Date()); + + if (isValid(localDateTimeValue)) { + return localDateTimeValue; + } + } + + const isoValue = parseISO(value); + + if (isValid(isoValue)) { + return isoValue; } } return null; } + @action updateValue(value) { + this.syncValue(value); + } + /** - * Update component value + * Update component value. * * @param {*} prop * @param {*} { target } * @memberof DateTimeInputComponent */ @action update(prop, { target }) { - const { onUpdate } = this.args; + const { onUpdate, onChange } = this.args; let { dateTimeFormat, date, time } = this; let { value } = target; - let dateTime, dateTimeInstance; + let dateTimeInstance; + + this[prop] = value; if (prop === 'time') { - if (date) { - dateTimeInstance = parse(`${date} ${value}`, dateTimeFormat, new Date()); - } else { - dateTimeInstance = parse(`${value}`, this.timeFormat, new Date()); - } + time = value; + dateTimeInstance = date ? parse(`${date} ${time}`, dateTimeFormat, new Date()) : parse(`${time}`, this.timeFormat, new Date()); } if (prop === 'date') { - if (time) { - dateTimeInstance = parse(`${value} ${time}`, dateTimeFormat, new Date()); - } else { - dateTimeInstance = parse(`${value}`, this.dateFormat, new Date()); + date = value; + dateTimeInstance = time ? parse(`${date} ${time}`, dateTimeFormat, new Date()) : parse(`${date}`, this.dateFormat, new Date()); + } + + if (!dateTimeInstance || !isValid(dateTimeInstance)) { + if (typeof onUpdate === 'function') { + onUpdate(null, null); } + + if (typeof onChange === 'function') { + onChange(null, null); + } + + return; } - dateTime = format(dateTimeInstance, dateTimeFormat); + const dateTime = format(dateTimeInstance, dateTimeFormat); if (typeof onUpdate === 'function') { onUpdate(dateTimeInstance, dateTime); } + + if (typeof onChange === 'function') { + onChange(dateTimeInstance, dateTime); + } } } diff --git a/addon/components/layout/header.hbs b/addon/components/layout/header.hbs index a373a3ed..0f85afe5 100644 --- a/addon/components/layout/header.hbs +++ b/addon/components/layout/header.hbs @@ -18,11 +18,7 @@ Pass `@mutateMenuItems={{fn}}` to mutate items before rendering (same contract as the previous static implementation). --}} - + {{/unless}}
{{yield}} @@ -32,7 +28,10 @@
-
+
+ + + @@ -63,4 +62,4 @@
- + \ No newline at end of file diff --git a/addon/components/layout/resource/card.js b/addon/components/layout/resource/card.js index bc5a63d5..20b2d1e5 100644 --- a/addon/components/layout/resource/card.js +++ b/addon/components/layout/resource/card.js @@ -10,7 +10,7 @@ export default class LayoutResourceCardComponent extends Component { } get cardClass() { - const baseClasses = 'bg-white dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow duration-200 overflow-hidden'; + const baseClasses = 'bg-gray-50 dark:bg-gray-700/50 rounded-md border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md transition-shadow duration-200 overflow-hidden'; return this.args.class ? `${baseClasses} ${this.args.class}` : baseClasses; } diff --git a/addon/components/lazy-engine-component.hbs b/addon/components/lazy-engine-component.hbs index ce062274..003d8f3e 100644 --- a/addon/components/lazy-engine-component.hbs +++ b/addon/components/lazy-engine-component.hbs @@ -5,4 +5,4 @@ {{component this.resolvedComponent params=this.params}} {{/if}} {{/if}} -
\ No newline at end of file + -
\ No newline at end of file + diff --git a/addon/components/table/cell/dropdown.js b/addon/components/table/cell/dropdown.js index c3bbc792..1456a620 100644 --- a/addon/components/table/cell/dropdown.js +++ b/addon/components/table/cell/dropdown.js @@ -7,7 +7,7 @@ export default class TableCellDropdownComponent extends Component { defaultButtonText = 'Actions'; @computed('args.column.ddButtonText', 'defaultButtonText') get buttonText() { - const { ddButtonText } = this.args.column; + const { ddButtonText } = this.args.column ?? {}; if (ddButtonText === undefined) { return this.defaultButtonText; @@ -20,25 +20,43 @@ export default class TableCellDropdownComponent extends Component { return ddButtonText; } + get renderInPlace() { + return this.args.column?.renderInPlace ?? true; + } + @action setupComponent(dropdownWrapperNode) { const tableCellNode = this.getOwnerTableCell(dropdownWrapperNode); - tableCellNode.style.overflow = 'visible'; + + if (tableCellNode) { + tableCellNode.style.overflow = 'visible'; + } + this.tableCellNode = tableCellNode; } @action onOpen() { - this.tableCellNode.style.zIndex = parseInt(this.tableCellNode.style.zIndex) + 1; + if (!this.tableCellNode) { + return; + } + + const currentZIndex = Number.parseInt(this.tableCellNode.style.zIndex, 10) || 0; + this.tableCellNode.style.zIndex = currentZIndex + 1; } @action onClose() { - this.tableCellNode.style.zIndex = parseInt(this.tableCellNode.style.zIndex) - 1; + if (!this.tableCellNode) { + return; + } + + const currentZIndex = Number.parseInt(this.tableCellNode.style.zIndex, 10) || 1; + this.tableCellNode.style.zIndex = currentZIndex - 1; } @action getOwnerTableCell(dropdownWrapperNode) { while (dropdownWrapperNode) { dropdownWrapperNode = dropdownWrapperNode.parentNode; - if (dropdownWrapperNode.tagName.toLowerCase() === 'td') { + if (dropdownWrapperNode?.tagName?.toLowerCase() === 'td') { return dropdownWrapperNode; } } @@ -57,15 +75,19 @@ export default class TableCellDropdownComponent extends Component { } @action calculatePosition(trigger, content) { + if (typeof this.args.column?.calculatePosition === 'function') { + return this.args.column.calculatePosition(trigger, content); + } + const triggerRect = trigger.getBoundingClientRect(); const contentRect = content?.getBoundingClientRect?.(); const contentWidth = contentRect?.width || 224; - let style = { + const style = { position: 'fixed', marginTop: '0px', - left: `${triggerRect.left - contentWidth - 3}px`, - top: `${triggerRect.top}px`, + left: triggerRect.left - contentWidth - 3, + top: triggerRect.top, }; return { style }; diff --git a/addon/components/table/cell/dropdown/action-item.hbs b/addon/components/table/cell/dropdown/action-item.hbs index 50c58907..78ec7a91 100644 --- a/addon/components/table/cell/dropdown/action-item.hbs +++ b/addon/components/table/cell/dropdown/action-item.hbs @@ -1,8 +1,8 @@ {{#if this.visible}} - {{#if @columnAction.separator}} -
- {{else}} - {{#if this.isVisible}} + {{#if this.isVisible}} + {{#if @columnAction.separator}} +
+ {{else}}
{{/if}} {{/if}} -{{/if}} \ No newline at end of file +{{/if}} diff --git a/addon/helpers/format-json.js b/addon/helpers/format-json.js new file mode 100644 index 00000000..3bef03b8 --- /dev/null +++ b/addon/helpers/format-json.js @@ -0,0 +1,5 @@ +import { helper } from '@ember/component/helper'; + +export default helper(function formatJson([jsonable]) { + return JSON.stringify(jsonable, null, 2); +}); diff --git a/addon/styles/components/dashboard.css b/addon/styles/components/dashboard.css index 3b30dcb3..2d9304d9 100644 --- a/addon/styles/components/dashboard.css +++ b/addon/styles/components/dashboard.css @@ -103,13 +103,13 @@ transform 180ms ease-out, box-shadow 180ms ease-out, border-color 180ms ease-out; - background-color: theme('colors.white'); - border-color: theme('colors.gray.200'); + background-color: #ffffff; + border-color: #e5e7eb; } body[data-theme='dark'] .fleet-ops-kpi-tile-pop { - background-color: theme('colors.gray.800'); - border-color: theme('colors.gray.700'); + background-color: #1f2937; + border-color: #374151; } .fleet-ops-kpi-tile-pop:hover { @@ -193,16 +193,16 @@ body[data-theme='dark'] .fleet-ops-kpi-tile-pop.kpi-accent-neutral { * - A neon-hued value number that pops against the dark theme. * ============================================================ */ .fleet-ops-pulse-tile { - background-color: theme('colors.gray.50'); - border-color: theme('colors.gray.200'); + background-color: #f9fafb; + border-color: #e5e7eb; transition: transform 160ms ease-out, box-shadow 160ms ease-out; } body[data-theme='dark'] .fleet-ops-pulse-tile { - background-color: theme('colors.gray.800'); - border-color: theme('colors.gray.700'); + background-color: #1f2937; + border-color: #374151; } .fleet-ops-pulse-tile:hover { diff --git a/addon/styles/components/tab-navigation.css b/addon/styles/components/tab-navigation.css index 4aed0096..34d5b906 100644 --- a/addon/styles/components/tab-navigation.css +++ b/addon/styles/components/tab-navigation.css @@ -1,27 +1,27 @@ :root { - --tab-bg-primary: theme('colors.white'); - --tab-bg-secondary: theme('colors.gray.50'); - --tab-border-color: theme('colors.gray.200'); - --tab-text-primary: theme('colors.gray.900'); - --tab-text-secondary: theme('colors.gray.600'); - --tab-text-hover: theme('colors.gray.900'); - --tab-hover-bg: theme('colors.gray.100'); - --tab-active-indicator: theme('colors.blue.500'); - --tab-focus-ring: theme('colors.blue.500'); - --tab-close-hover: theme('colors.gray.200'); + --tab-bg-primary: #ffffff; + --tab-bg-secondary: #f9fafb; + --tab-border-color: #e5e7eb; + --tab-text-primary: #111827; + --tab-text-secondary: #4b5563; + --tab-text-hover: #111827; + --tab-hover-bg: #f3f4f6; + --tab-active-indicator: #3b82f6; + --tab-focus-ring: #3b82f6; + --tab-close-hover: #e5e7eb; } body[data-theme='dark'] { - --tab-bg-primary: theme('colors.gray.900'); - --tab-bg-secondary: theme('colors.gray.800'); - --tab-border-color: theme('colors.gray.700'); - --tab-text-primary: theme('colors.white'); - --tab-text-secondary: theme('colors.gray.400'); - --tab-text-hover: theme('colors.white'); - --tab-hover-bg: theme('colors.gray.700'); - --tab-active-indicator: theme('colors.blue.500'); - --tab-focus-ring: theme('colors.blue.500'); - --tab-close-hover: theme('colors.gray.600'); + --tab-bg-primary: #111827; + --tab-bg-secondary: #1f2937; + --tab-border-color: #374151; + --tab-text-primary: #ffffff; + --tab-text-secondary: #9ca3af; + --tab-text-hover: #ffffff; + --tab-hover-bg: #374151; + --tab-active-indicator: #3b82f6; + --tab-focus-ring: #3b82f6; + --tab-close-hover: #4b5563; } .tab-navigation { @@ -269,7 +269,7 @@ body[data-theme='dark'] .tab-badge { @media (prefers-contrast: high) { .tab-navigation[data-style='github'] .tab-item--active { - --tab-active-indicator: theme('colors.blue.600'); + --tab-active-indicator: #2563eb; } .tab-navigation .tab-item { @@ -340,7 +340,7 @@ body[data-theme='dark'] .tab-badge { } body[data-theme='dark'] .tab-overflow-menu .tab-overflow-menu-item { - color: theme('colors.gray.200'); + color: #e5e7eb; } .tab-overflow-menu-icon { diff --git a/addon/styles/layout/next.css b/addon/styles/layout/next.css index bbb9b482..8146e527 100644 --- a/addon/styles/layout/next.css +++ b/addon/styles/layout/next.css @@ -1742,6 +1742,49 @@ body[data-theme='dark'] @apply bg-blue-600; } +#view-header-tray-items > .next-user-button { + display: flex; + align-items: center; + margin: 0; +} + +#view-header-tray-items > .next-user-button > .ember-basic-dropdown { + display: flex; + align-items: center; +} + +#view-header-tray-items > .next-user-button > .ember-basic-dropdown > .ember-basic-dropdown-trigger, +#view-header-tray-items > .next-user-button > .ember-basic-dropdown > .ember-basic-dropdown-trigger > .local-selector-tray-trigger, +#view-header-tray-items > .next-user-button > button { + display: flex; + width: 40px; + height: 31px; + box-sizing: border-box; + align-items: center; + justify-content: center; + margin: 0; + border: 0; + background: transparent; + padding: 0; + font: inherit; + line-height: 1; +} + +#view-header-tray-items > .next-user-button > button { + appearance: none; +} + +#view-header-tray-items > .next-user-button .next-org-button-trigger { + display: flex; + width: 40px; + height: 31px; + max-height: 31px; + box-sizing: border-box; + align-items: center; + justify-content: center; + padding: 0; +} + .chat-tray-panel-container.is-mobile > .next-dd-menu, .notification-tray-panel-container.is-mobile > .next-dd-menu, .notification-tray-panel-container.is-mobile > .notification-tray-panel, @@ -1939,6 +1982,11 @@ section.next-view-section { overflow: hidden; } +.next-view-section-body.scrollable, +.next-view-section-body.overflow-y-scroll { + overflow-y: scroll !important; +} + .next-view-section-body > .next-table-wrapper { flex: 1 1 auto; min-width: 0; diff --git a/app/helpers/format-json.js b/app/helpers/format-json.js new file mode 100644 index 00000000..d1926219 --- /dev/null +++ b/app/helpers/format-json.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/ember-ui/helpers/format-json'; diff --git a/package.json b/package.json index c7e3e8b6..76f95177 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fleetbase/ember-ui", - "version": "0.3.38", + "version": "0.3.39", "description": "Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.", "keywords": [ "fleetbase-ui", @@ -19,7 +19,9 @@ "scripts": { "build": "ember build --environment=production", "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"", - "lint:css": "stylelint \"**/*.css\"", + "check:css-alpha": "node scripts/normalize-rgba-alpha.js --check", + "fix:css-alpha": "node scripts/normalize-rgba-alpha.js", + "lint:css": "npm run check:css-alpha && stylelint \"**/*.css\"", "lint:css:fix": "concurrently \"npm:lint:css -- --fix\"", "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"", "lint:hbs": "ember-template-lint .", diff --git a/scripts/normalize-rgba-alpha.js b/scripts/normalize-rgba-alpha.js new file mode 100644 index 00000000..b8fcd858 --- /dev/null +++ b/scripts/normalize-rgba-alpha.js @@ -0,0 +1,89 @@ +#!/usr/bin/env node +/* eslint-env node */ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const root = process.cwd(); +const checkOnly = process.argv.includes('--check'); +const cssRoots = ['addon', 'app', 'tests'].map((dir) => path.join(root, dir)).filter((dir) => fs.existsSync(dir)); +const rgbaPercentAlphaPattern = /rgba\(\s*([+-]?(?:\d*\.)?\d+)\s*,\s*([+-]?(?:\d*\.)?\d+)\s*,\s*([+-]?(?:\d*\.)?\d+)\s*,\s*([+-]?(?:\d*\.)?\d+)%\s*\)/g; + +function collectCssFiles(dir, files = []) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + collectCssFiles(fullPath, files); + continue; + } + + if (entry.isFile() && entry.name.endsWith('.css')) { + files.push(fullPath); + } + } + + return files; +} + +function formatAlpha(percent) { + const alpha = Number(percent) / 100; + return Number.isInteger(alpha) ? String(alpha) : String(Number(alpha.toFixed(4))); +} + +function lineForIndex(source, index) { + return source.slice(0, index).split('\n').length; +} + +const changes = []; + +for (const cssRoot of cssRoots) { + for (const filePath of collectCssFiles(cssRoot)) { + const source = fs.readFileSync(filePath, 'utf8'); + const matches = []; + + const next = source.replace(rgbaPercentAlphaPattern, (match, red, green, blue, alphaPercent, offset) => { + const replacement = `rgba(${red}, ${green}, ${blue}, ${formatAlpha(alphaPercent)})`; + matches.push({ + line: lineForIndex(source, offset), + before: match, + after: replacement, + }); + return replacement; + }); + + if (matches.length === 0) { + continue; + } + + changes.push({ + filePath, + matches, + }); + + if (!checkOnly) { + fs.writeFileSync(filePath, next); + } + } +} + +if (changes.length === 0) { + console.log('No comma-form rgba() percent-alpha values found.'); + process.exit(0); +} + +for (const change of changes) { + const relativePath = path.relative(root, change.filePath); + + for (const match of change.matches) { + console.log(`${relativePath}:${match.line} ${match.before} -> ${match.after}`); + } +} + +if (checkOnly) { + console.error('\nRun `npm run fix:css-alpha` to normalize these values before production builds.'); + process.exit(1); +} + +console.log(`\nNormalized ${changes.reduce((total, change) => total + change.matches.length, 0)} rgba() alpha value(s).`); diff --git a/tests/integration/components/date-time-input-test.js b/tests/integration/components/date-time-input-test.js index c93a0013..d0bf0ffb 100644 --- a/tests/integration/components/date-time-input-test.js +++ b/tests/integration/components/date-time-input-test.js @@ -1,6 +1,6 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'dummy/tests/helpers'; -import { fillIn, render } from '@ember/test-helpers'; +import { fillIn, render, settled } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; module('Integration | Component | date-time-input', function (hooks) { @@ -30,6 +30,15 @@ module('Integration | Component | date-time-input', function (hooks) { assert.dom('[aria-label="Time Input"]').hasValue('18:47'); }); + test('it renders an iso date-time string value', async function (assert) { + this.set('value', '2026-07-03T15:00:00+08:00'); + + await render(hbs``); + + assert.dom('[aria-label="Date Input"]').hasValue('2026-07-03'); + assert.dom('[aria-label="Time Input"]').hasValue('15:00'); + }); + test('it renders empty inputs for an invalid date-time string value', async function (assert) { this.set('value', 'not a date'); @@ -68,4 +77,46 @@ module('Integration | Component | date-time-input', function (hooks) { await render(hbs``); await fillIn('[aria-label="Time Input"]', '19:12'); }); + + test('it keeps the latest date when changing time after date', async function (assert) { + assert.expect(2); + + const changes = []; + this.set('value', '2026-06-18 18:47'); + this.set('onUpdate', (_dateTimeInstance, dateTime) => { + changes.push(dateTime); + }); + + await render(hbs``); + await fillIn('[aria-label="Date Input"]', '2026-06-19'); + await fillIn('[aria-label="Time Input"]', '19:12'); + + assert.strictEqual(changes[0], '2026-06-19 18:47'); + assert.strictEqual(changes[1], '2026-06-19 19:12'); + }); + + test('it calls onChange for legacy callers', async function (assert) { + assert.expect(2); + + this.set('value', '2026-06-18 18:47'); + this.set('onChange', (dateTimeInstance, dateTime) => { + assert.true(dateTimeInstance instanceof Date); + assert.strictEqual(dateTime, '2026-06-18 19:12'); + }); + + await render(hbs``); + await fillIn('[aria-label="Time Input"]', '19:12'); + }); + + test('it syncs when the external value changes', async function (assert) { + this.set('value', '2026-06-18 18:47'); + + await render(hbs``); + + this.set('value', '2026-07-03T15:00:00+08:00'); + await settled(); + + assert.dom('[aria-label="Date Input"]').hasValue('2026-07-03'); + assert.dom('[aria-label="Time Input"]').hasValue('15:00'); + }); }); diff --git a/tests/integration/components/table/cell/dropdown-test.js b/tests/integration/components/table/cell/dropdown-test.js index 23548b65..f6957eb1 100644 --- a/tests/integration/components/table/cell/dropdown-test.js +++ b/tests/integration/components/table/cell/dropdown-test.js @@ -23,4 +23,49 @@ module('Integration | Component | table/cell/dropdown', function (hooks) { assert.dom(this.element).hasText('template block text'); }); + + test('it supports custom and default dropdown positioning', function (assert) { + const ComponentClass = this.owner.factoryFor('component:table/cell/dropdown').class; + const trigger = { + getBoundingClientRect() { + return { left: 500, top: 120 }; + }, + }; + const content = { + getBoundingClientRect() { + return { width: 220 }; + }, + }; + const defaultComponent = new ComponentClass(this.owner, { column: {} }); + const customPosition = { style: { left: 10, top: 20 } }; + const customComponent = new ComponentClass(this.owner, { + column: { + calculatePosition(receivedTrigger, receivedContent) { + assert.strictEqual(receivedTrigger, trigger, 'custom calculator receives trigger'); + assert.strictEqual(receivedContent, content, 'custom calculator receives content'); + + return customPosition; + }, + }, + }); + + assert.deepEqual(defaultComponent.calculatePosition(trigger, content), { + style: { + position: 'fixed', + marginTop: '0px', + left: 277, + top: 120, + }, + }); + assert.strictEqual(customComponent.calculatePosition(trigger, content), customPosition, 'custom calculator result is used'); + }); + + test('it defaults renderInPlace to true and preserves explicit false', function (assert) { + const ComponentClass = this.owner.factoryFor('component:table/cell/dropdown').class; + const defaultComponent = new ComponentClass(this.owner, { column: {} }); + const disabledInPlaceComponent = new ComponentClass(this.owner, { column: { renderInPlace: false } }); + + assert.true(defaultComponent.renderInPlace, 'renderInPlace defaults to true'); + assert.false(disabledInPlaceComponent.renderInPlace, 'explicit renderInPlace false is preserved'); + }); }); diff --git a/tests/integration/components/table/cell/dropdown/action-item-test.js b/tests/integration/components/table/cell/dropdown/action-item-test.js index 19598a86..1ec08eb4 100644 --- a/tests/integration/components/table/cell/dropdown/action-item-test.js +++ b/tests/integration/components/table/cell/dropdown/action-item-test.js @@ -23,4 +23,26 @@ module('Integration | Component | table/cell/dropdown/action-item', function (ho assert.dom().hasText('template block text'); }); + + test('separator obeys isVisible', async function (assert) { + this.set('row', { id: 'row_1', shouldShowSeparator: true }); + this.set('visibleSeparator', { + separator: true, + isVisible(row) { + return row.shouldShowSeparator; + }, + }); + this.set('hiddenSeparator', { + separator: true, + isVisible() { + return false; + }, + }); + + await render(hbs``); + assert.dom('.next-dd-menu-seperator').exists('visible separator renders'); + + await render(hbs``); + assert.dom('.next-dd-menu-seperator').doesNotExist('hidden separator does not render'); + }); }); diff --git a/tests/integration/helpers/format-json-test.js b/tests/integration/helpers/format-json-test.js new file mode 100644 index 00000000..5c2c087a --- /dev/null +++ b/tests/integration/helpers/format-json-test.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'dummy/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Helper | format-json', function (hooks) { + setupRenderingTest(hooks); + + // TODO: Replace this with your real tests. + test('it renders', async function (assert) { + this.set('inputValue', '1234'); + + await render(hbs`{{format-json this.inputValue}}`); + + assert.dom().hasText('1234'); + }); +});