From c3fa8854474557091339e0f04f564ac6f674e090 Mon Sep 17 00:00:00 2001 From: Alex LaFroscia Date: Tue, 29 Jan 2019 15:25:13 -0800 Subject: [PATCH] feat: add `from-event` element modifier` --- addon/modifiers/from-event.js | 71 ++++++++++ app/modifiers/from-event.js | 1 + package.json | 1 + .../integration/modifiers/from-event-test.js | 134 ++++++++++++++++++ yarn.lock | 59 ++++++++ 5 files changed, 266 insertions(+) create mode 100644 addon/modifiers/from-event.js create mode 100644 app/modifiers/from-event.js create mode 100644 tests/integration/modifiers/from-event-test.js diff --git a/addon/modifiers/from-event.js b/addon/modifiers/from-event.js new file mode 100644 index 0000000..ac4d3a5 --- /dev/null +++ b/addon/modifiers/from-event.js @@ -0,0 +1,71 @@ +import Ember from "ember"; +import { fromEvent } from "rxjs"; + +export default Ember._setModifierManager( + () => ({ + createModifier() { + return { + subscription: undefined, + element: undefined + }; + }, + + _setupSubscription(state, eventName, operatorOrObserver, maybeObserver) { + const { element } = state; + let operator, observer; + + if (operatorOrObserver && maybeObserver) { + operator = operatorOrObserver; + observer = maybeObserver; + } else if (operatorOrObserver && !maybeObserver) { + observer = operatorOrObserver; + } + + let observable = fromEvent(element, eventName); + + if (operator) { + observable = observable.pipe(operator); + } + + state.subscription = observable.subscribe(observer); + }, + + installModifier( + state, + element, + { + positional: [eventName, operatorOrObserver, maybeObserver] + } + ) { + state.element = element; + + this._setupSubscription( + state, + eventName, + operatorOrObserver, + maybeObserver + ); + }, + + updateModifier( + state, + { + positional: [eventName, operatorOrSubscribe, maybeObserver] + } + ) { + state.subscription.unsubscribe(); + + this._setupSubscription( + state, + eventName, + operatorOrSubscribe, + maybeObserver + ); + }, + + destroyModifier({ subscription }) { + subscription.unsubscribe(); + } + }), + class FromEventModifier {} +); diff --git a/app/modifiers/from-event.js b/app/modifiers/from-event.js new file mode 100644 index 0000000..7c78730 --- /dev/null +++ b/app/modifiers/from-event.js @@ -0,0 +1 @@ +export { default } from "ember-rx/modifiers/from-event"; diff --git a/package.json b/package.json index 7913d2a..eaed7c9 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "ember-export-application-global": "^2.0.0", "ember-load-initializers": "^1.1.0", "ember-maybe-import-regenerator": "^0.1.6", + "ember-modifier-manager-polyfill": "^1.0.2", "ember-qunit": "^3.4.1", "ember-resolver": "^5.0.1", "ember-source": "~3.7.0", diff --git a/tests/integration/modifiers/from-event-test.js b/tests/integration/modifiers/from-event-test.js new file mode 100644 index 0000000..24c17af --- /dev/null +++ b/tests/integration/modifiers/from-event-test.js @@ -0,0 +1,134 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "ember-qunit"; +import { setupScheduler } from "ember-rx/test-support"; +import { render, click } from "@ember/test-helpers"; +import { Subject } from "rxjs"; +import { filter, scan } from "rxjs/operators"; +import hbs from "htmlbars-inline-precompile"; +import td from "testdouble"; + +module("Integration | Modifier | from-event", function(hooks) { + setupRenderingTest(hooks); + setupScheduler(hooks); + + test("it can subscribe to events", async function(assert) { + this.observer = td.function(); + + await render(hbs` + + `); + + await click("button"); + + assert.verify( + this.observer(td.matchers.isA(MouseEvent)), + "The observer was called with the event" + ); + }); + + test("it can pipe the observable through an operator", async function(assert) { + this.operator = filter((_event, index) => index % 2 === 0); + this.observer = td.function(); + + await render(hbs` + + `); + + await click("button"); + await click("button"); + + assert.verify( + this.observer(td.matchers.isA(MouseEvent)), + { times: 1 }, + "The observer was called one time" + ); + }); + + test("it handles the arguments changing", async function(assert) { + const originalObserver = td.function("original observer"); + const newObserver = td.function("new observer"); + + this.observer = originalObserver; + + await render(hbs` + + `); + + this.set("observer", newObserver); + + await click("button"); + + assert.verify( + originalObserver(td.matchers.isA(MouseEvent)), + { times: 0 }, + "The original observer is never called" + ); + + assert.verify( + newObserver(td.matchers.isA(MouseEvent)), + { times: 1 }, + "The new observer is called" + ); + }); + + test("it can receive a `Subject` to surface the observable", async function(assert) { + this.subject = new Subject(); + const observer = td.function("Original observer"); + + this.subject.subscribe(observer); + + await render(hbs` + + `); + + await click("button"); + + assert.verify( + observer(td.matchers.isA(MouseEvent)), + "The observer is called through the Subject" + ); + }); + + module("cookbook", function() { + test("adding to a list with each click", async function(assert) { + this.subject = new Subject(); + this.accumulate = scan((acc, event) => [...acc, event], []); + + await render(hbs` + + + + `); + + assert + .dom("[data-test-index]") + .exists({ count: 0 }, "Renders no elements"); + + await click("button"); + + assert + .dom("[data-test-index]") + .exists({ count: 1 }, "Renders one element"); + + await click("button"); + + assert + .dom("[data-test-index]") + .exists({ count: 2 }, "Renders two elements"); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 5c39b42..e5690cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2412,6 +2412,23 @@ broccoli-babel-transpiler@^7.1.2: rsvp "^4.8.3" workerpool "^2.3.1" +broccoli-babel-transpiler@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-7.1.2.tgz#fb5d6f8b9a805627ac3f2914ac9d86e82ca2413b" + integrity sha512-rljx86xgZJ2BjWt+xCSVfvyt3ONpCdMMXzMpeeVpAGdBHj3bqQICdPHZDAbzn1vKY/LIPsJZftvdxql1jiLGzw== + dependencies: + "@babel/core" "^7.0.0" + "@babel/polyfill" "^7.0.0" + broccoli-funnel "^2.0.1" + broccoli-merge-trees "^3.0.0" + broccoli-persistent-filter "^1.4.3" + clone "^2.1.2" + hash-for-dep "^1.2.3" + heimdalljs-logger "^0.1.9" + json-stable-stringify "^1.0.1" + rsvp "^4.8.3" + workerpool "^2.3.1" + broccoli-bridge@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/broccoli-bridge/-/broccoli-bridge-1.0.0.tgz#6223fd64b62062c31333539f0f3c42d0acd92fb1" @@ -4752,6 +4769,30 @@ ember-cli-babel@^7.4.2: ensure-posix-path "^1.0.2" semver "^5.5.0" +ember-cli-babel@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.4.2.tgz#9d7daa165b509e41f6dc3bb443ae32072f766aa2" + integrity sha512-5PJOkQ3B3Cvef2nQVPuZSPA6ckwiED3qF4cqzu7jcKhZ0Fy2TwPqABVbiPBJ46NujAsMZrjverVRST74Q25GqQ== + dependencies: + "@babel/core" "^7.0.0" + "@babel/plugin-transform-modules-amd" "^7.0.0" + "@babel/plugin-transform-runtime" "^7.2.0" + "@babel/polyfill" "^7.0.0" + "@babel/preset-env" "^7.0.0" + "@babel/runtime" "^7.2.0" + amd-name-resolver "^1.2.1" + babel-plugin-debug-macros "^0.2.0-beta.6" + babel-plugin-ember-modules-api-polyfill "^2.6.0" + babel-plugin-module-resolver "^3.1.1" + broccoli-babel-transpiler "^7.1.2" + broccoli-debug "^0.6.4" + broccoli-funnel "^2.0.1" + broccoli-source "^1.1.0" + clone "^2.1.2" + ember-cli-version-checker "^2.1.2" + ensure-posix-path "^1.0.2" + semver "^5.5.0" + ember-cli-broccoli-sane-watcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-3.0.0.tgz#dc1812c047e1ceec4413d3c41b51a9ffc61b4cfe" @@ -5177,6 +5218,15 @@ ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2: ember-cli-version-checker "^2.1.1" semver "^5.4.1" +ember-compatibility-helpers@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ember-compatibility-helpers/-/ember-compatibility-helpers-1.2.0.tgz#feee16c5e9ef1b1f1e53903b241740ad4b01097e" + integrity sha512-pUW4MzJdcaQtwGsErYmitFRs0rlCYBAnunVzlFFUBr4xhjlCjgHJo0b53gFnhTgenNM3d3/NqLarzRhDTjXRTg== + dependencies: + babel-plugin-debug-macros "^0.2.0" + ember-cli-version-checker "^2.1.1" + semver "^5.4.1" + ember-component-css@^0.6.7: version "0.6.7" resolved "https://registry.yarnpkg.com/ember-component-css/-/ember-component-css-0.6.7.tgz#dbc6debe5c04a2c0fe8a5edc64303c7324b33945" @@ -5374,6 +5424,15 @@ ember-modal-dialog@2.4.3: ember-ignore-children-helper "^1.0.0" ember-wormhole "^0.5.1" +ember-modifier-manager-polyfill@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ember-modifier-manager-polyfill/-/ember-modifier-manager-polyfill-1.0.2.tgz#c0b2da9816aad56fb1398023185c249cf76fe4bc" + integrity sha512-zu0gcSbQ7aOAtGFPQbMPQ1XKU2/NUBN6+Px65L9r4HxEzKXNUd76o1HE5p4QhLS1V6rNCD7UNmsi5r57ar0Rag== + dependencies: + ember-cli-babel "^7.4.2" + ember-cli-version-checker "^2.1.2" + ember-compatibility-helpers "^1.2.0" + ember-qunit@^3.4.1: version "3.5.3" resolved "https://registry.yarnpkg.com/ember-qunit/-/ember-qunit-3.5.3.tgz#bfd0bff8298c78c77e870cca43fe0826e78a0d09"