diff --git a/ember-resources/package.json b/ember-resources/package.json index ff9dc5ca5..33a9faf4c 100644 --- a/ember-resources/package.json +++ b/ember-resources/package.json @@ -13,6 +13,7 @@ "./core/class-based": "./dist/core/class-based/index.js", "./core/function-based": "./dist/core/function-based/index.js", "./util": "./dist/util/index.js", + "./util/cell": "./dist/util/cell.js", "./util/map": "./dist/util/map.js", "./util/helper": "./dist/util/helper.js", "./util/remote-data": "./dist/util/remote-data.js", @@ -33,6 +34,9 @@ "util": [ "dist/util/index.d.ts" ], + "util/cell": [ + "dist/util/cell.d.ts" + ], "util/function": [ "dist/util/function.d.ts" ], diff --git a/ember-resources/src/index.ts b/ember-resources/src/index.ts index 4a396c02c..eaa103d9e 100644 --- a/ember-resources/src/index.ts +++ b/ember-resources/src/index.ts @@ -3,6 +3,9 @@ export { Resource } from './core/class-based'; export { resource, resourceFactory } from './core/function-based'; export { use } from './core/use'; +// Public API -- Utilities +export { cell } from 'util/cell'; + // Public Type Utilities export type { ExpandArgs } from './core/class-based/types'; export type { ArgsWrapper, Thunk } from './core/types'; diff --git a/ember-resources/src/util/cell.ts b/ember-resources/src/util/cell.ts new file mode 100644 index 000000000..143aa2351 --- /dev/null +++ b/ember-resources/src/util/cell.ts @@ -0,0 +1,57 @@ +import { tracked } from '@glimmer/tracking'; +import { assert } from '@ember/debug'; + +class Cell { + @tracked current: T; + + constructor(initialValue: T) { + this.current = initialValue; + } + + toggle = () => { + assert( + `toggle can only be used when 'current' is a boolean type`, + typeof this.current === 'boolean' || this.current === undefined + ); + + (this.current as boolean) = !this.current; + }; +} + +/** + * Small state utility for helping reduce the number of imports + * when working with resources in isolation. + * + * The return value is an instance of a class with a single + * `@tracked` property, `current`. If `current` is a boolean, + * there is a `toggle` method available as well. + * + * For example, a Clock: + * + * ```js + * import { resource, cell } from 'ember-resources'; + * + * const Clock = resource(({ on }) => { + * let time = cell(new Date()); + * let interval = setInterval(() => time.current = new Date(), 1000); + * + * on.cleanup(() => clearInterval(interval)); + * + * let formatter = new Intl.DateTimeFormat('en-US', { + * hour: 'numeric', + * minute: 'numeric', + * second: 'numeric', + * hour12: true, + * }); + * + * return () => formatter.format(time.current); + * }); + * + * + * ``` + */ +export function cell(initialValue: T): Cell { + return new Cell(initialValue); +} diff --git a/testing/ember-app/tests/utils/cell/rendering-test.ts b/testing/ember-app/tests/utils/cell/rendering-test.ts new file mode 100644 index 000000000..a197ce1ff --- /dev/null +++ b/testing/ember-app/tests/utils/cell/rendering-test.ts @@ -0,0 +1,26 @@ +import { render, settled } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; + +import { cell } from 'ember-resources'; + +module('Utils | cell | rendering', function (hooks) { + setupRenderingTest(hooks); + + test('it works', async function (assert) { + let state = cell(); + + this.setProperties({ state }); + + await render(hbs`{{this.state.current}}`); + + assert.dom().doesNotContainText('hello'); + + state.current = 'hello'; + + await settled(); + + assert.dom().hasText('hello'); + }); +});