mirror of https://github.com/status-im/consul.git
ui: Nestable DataSources (plus glimmer upgrade) (#9275)
This commit is contained in:
parent
9f0f2bd589
commit
9cf30e74e6
|
@ -2,11 +2,14 @@
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
<DataSource
|
<DataSource
|
||||||
@src="/dc/nspace/services"
|
@src="/nspace/dc/services"
|
||||||
@loading="eager"
|
@loading="eager"
|
||||||
|
@disabled={{false}}
|
||||||
@onchange={{action (mut items) value="data"}}
|
@onchange={{action (mut items) value="data"}}
|
||||||
@onerror={{action (mut error) value="error"}}
|
@onerror={{action (mut error) value="error"}}
|
||||||
/>
|
as |source|>
|
||||||
|
<source.Source @src="" />
|
||||||
|
</DataSource>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Arguments
|
### Arguments
|
||||||
|
@ -15,6 +18,7 @@
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `src` | `String` | | The source to subscribe to updates to, this should map to a string based URI |
|
| `src` | `String` | | The source to subscribe to updates to, this should map to a string based URI |
|
||||||
| `loading` | `String` | eager | Allows the browser to defer loading offscreen DataSources (`eager\|lazy`). Setting to `lazy` only loads the data when the DataSource is visible in the DOM (inc. `display: none\|block;`) |
|
| `loading` | `String` | eager | Allows the browser to defer loading offscreen DataSources (`eager\|lazy`). Setting to `lazy` only loads the data when the DataSource is visible in the DOM (inc. `display: none\|block;`) |
|
||||||
|
| `disabled` | `Boolean` | true | When disabled the DataSource is closed |
|
||||||
| `open` | `Boolean` | false | Force the DataSource to open, used to force non-blocking data to refresh (has no effect for blocking data) |
|
| `open` | `Boolean` | false | Force the DataSource to open, used to force non-blocking data to refresh (has no effect for blocking data) |
|
||||||
| `onchange` | `Function` | | The action to fire when the data changes. Emits an Event-like object with a `data` property containing the data. |
|
| `onchange` | `Function` | | The action to fire when the data changes. Emits an Event-like object with a `data` property containing the data. |
|
||||||
| `onerror` | `Function` | | The action to fire when an error occurs. Emits ErrorEvent object with an `error` property containing the Error. |
|
| `onerror` | `Function` | | The action to fire when an error occurs. Emits ErrorEvent object with an `error` property containing the Error. |
|
||||||
|
@ -30,23 +34,31 @@ Behind the scenes in the Consul UI we map URIs back to our `ember-data` backed `
|
||||||
`DataSource` is not just restricted to HTTP API data, and can be configured to listen for data changes using a variety of methods and sources. For example we have also configured `DataSource` to listen to `LocalStorage` changes using the `settings://` pseudo-protocol in the URI (See examples below).
|
`DataSource` is not just restricted to HTTP API data, and can be configured to listen for data changes using a variety of methods and sources. For example we have also configured `DataSource` to listen to `LocalStorage` changes using the `settings://` pseudo-protocol in the URI (See examples below).
|
||||||
|
|
||||||
|
|
||||||
### Example
|
### Examples
|
||||||
|
|
||||||
Straightforward usage can use `mut` to easily update data within a template
|
Straightforward usage can use `mut` to easily update data within a template using an event handler approach.
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{! listen for HTTP API changes}}
|
{{! listen for HTTP API changes}}
|
||||||
<DataSource @src="/dc/nspace/services"
|
<DataSource
|
||||||
|
@src="/nspace/dc/services"
|
||||||
@onchange={{action (mut items) value="data"}}
|
@onchange={{action (mut items) value="data"}}
|
||||||
@onerror={{action (mut error) value="error"}}
|
@onerror={{action (mut error) value="error"}}
|
||||||
/>
|
/>
|
||||||
|
{{#if error}}
|
||||||
|
Something went wrong!
|
||||||
|
{{/if}}
|
||||||
|
{{#if (not items)}}
|
||||||
|
Loading...
|
||||||
|
{{/if}}
|
||||||
{{! the value of items will change whenever the data changes}}
|
{{! the value of items will change whenever the data changes}}
|
||||||
{{#each items as |item|}}
|
{{#each items as |item|}}
|
||||||
{{item.Name}} {{! < Prints the item name }}
|
{{item.Name}} {{! < Prints the item name }}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
{{! listen for Settings (local storage) changes}}
|
{{! listen for Settings (local storage) changes}}
|
||||||
<DataSource @src="settings://consul:token"
|
<DataSource
|
||||||
|
@src="settings://consul:token"
|
||||||
@onchange={{action (mut token) value="data"}}
|
@onchange={{action (mut token) value="data"}}
|
||||||
@onerror={{action (mut error) value="error"}}
|
@onerror={{action (mut error) value="error"}}
|
||||||
/>
|
/>
|
||||||
|
@ -54,6 +66,67 @@ Straightforward usage can use `mut` to easily update data within a template
|
||||||
{{token.AccessorID}} {{! < Prints the token AccessorID }}
|
{{token.AccessorID}} {{! < Prints the token AccessorID }}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A property approach to easily update data within a template
|
||||||
|
|
||||||
|
```handlebars
|
||||||
|
{{! listen for HTTP API changes}}
|
||||||
|
<DataSource
|
||||||
|
@src="/nspace/dc/services"
|
||||||
|
as |source|>
|
||||||
|
{{#if source.error}}
|
||||||
|
Something went wrong!
|
||||||
|
{{/if}}
|
||||||
|
{{#if (not source.data)}}
|
||||||
|
Loading...
|
||||||
|
{{/if}}
|
||||||
|
{{! the value of items will change whenever the data changes}}
|
||||||
|
{{#each source.data as |item|}}
|
||||||
|
{{item.Name}} {{! < Prints the item name }}
|
||||||
|
{{/each}}
|
||||||
|
</DataSource>
|
||||||
|
```
|
||||||
|
|
||||||
|
Both approaches can be used in tandem.
|
||||||
|
|
||||||
|
DataSources can also be recursively nested for loading in series as opposed to in parallel. Nested DataSources will not start loading until the immediate parent has loaded (ie. it has data) as they are not placed into the DOM until this has happened. However, if a DataSource has started loading, and the immediate parent errors, the nested DataSource will stop receiving updates yet it and its properties will remain accessible within the DOM.
|
||||||
|
|
||||||
|
```handlebars
|
||||||
|
|
||||||
|
{{! straightforwards error/loading states}}
|
||||||
|
{{#if error}}
|
||||||
|
Something went wrong!
|
||||||
|
{{else if (not loaded)}}
|
||||||
|
Loading...
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{! listen for HTTP API changes}}
|
||||||
|
<DataSource
|
||||||
|
@src="/nspace/dc/services"
|
||||||
|
@onerror={{action (mut error) value="error"}}
|
||||||
|
as |source|>
|
||||||
|
|
||||||
|
<source.Source
|
||||||
|
@src="/nspace/dc/service/{{source.data.firstObject.Name}}"
|
||||||
|
@onerror={{action (mut error) value="error"}}
|
||||||
|
as |source|>
|
||||||
|
|
||||||
|
{{source.data.Service.Service.Name}} <== Detailed information for the first service
|
||||||
|
|
||||||
|
<source.Source
|
||||||
|
@src="/nspace/dc/proxy/for-service/{{source.data.Service.ID}}"
|
||||||
|
@onerror={{action (mut error) value="error"}}
|
||||||
|
@onchange={{action (mut loaded) true}}
|
||||||
|
as |source|>
|
||||||
|
|
||||||
|
{{source.data.DestinationName}}
|
||||||
|
|
||||||
|
</source.Source>
|
||||||
|
|
||||||
|
</source.Source>
|
||||||
|
|
||||||
|
</DataSource>
|
||||||
|
```
|
||||||
|
|
||||||
### See
|
### See
|
||||||
|
|
||||||
- [Component Source Code](./index.js)
|
- [Component Source Code](./index.js)
|
||||||
|
|
|
@ -1,4 +1,24 @@
|
||||||
{{#if (eq loading "lazy")}}
|
{{#if (not this.disabled)}}
|
||||||
{{! in order to use intersection observer we need a DOM element on the page}}
|
{{#if (eq this.loading "lazy")}}
|
||||||
<data id={{guid}} aria-hidden="true" style="width: 0;height: 0;font-size: 0;padding: 0;margin: 0;" />
|
{{! in order to use intersection observer we need a DOM element on the page}}
|
||||||
|
<data
|
||||||
|
{{did-insert this.connect}}
|
||||||
|
aria-hidden="true"
|
||||||
|
style="width: 0;height: 0;font-size: 0;padding: 0;margin: 0;"
|
||||||
|
/>
|
||||||
|
{{else}}
|
||||||
|
{{did-insert this.connect}}
|
||||||
|
{{/if}}
|
||||||
|
{{did-update this.attributeChanged 'src' @src}}
|
||||||
|
{{did-update this.attributeChanged 'loading' @loading}}
|
||||||
|
{{will-destroy this.disconnect}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{did-update this.attributeChanged 'disabled' @disabled}}
|
||||||
|
{{yield (hash
|
||||||
|
data=this.data
|
||||||
|
error=this.error
|
||||||
|
Source=(if this.data
|
||||||
|
(component 'data-source' disabled=(not (eq this.error undefined)))
|
||||||
|
''
|
||||||
|
)
|
||||||
|
)}}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Component from '@ember/component';
|
import Component from '@glimmer/component';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { set, get } from '@ember/object';
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import { action, get } from '@ember/object';
|
||||||
import { schedule } from '@ember/runloop';
|
import { schedule } from '@ember/runloop';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,119 +23,160 @@ const replace = function(
|
||||||
if (prev !== value) {
|
if (prev !== value) {
|
||||||
destroy(prev, value);
|
destroy(prev, value);
|
||||||
}
|
}
|
||||||
return set(obj, prop, value);
|
return (obj[prop] = value);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Component.extend({
|
const noop = () => {};
|
||||||
tagName: '',
|
const optional = op => (typeof op === 'function' ? op : noop);
|
||||||
|
|
||||||
data: service('data-source/service'),
|
// possible values for @loading=""
|
||||||
dom: service('dom'),
|
const LOADING = ['eager', 'lazy'];
|
||||||
logger: service('logger'),
|
|
||||||
|
|
||||||
onchange: function(e) {},
|
export default class DataSource extends Component {
|
||||||
onerror: function(e) {},
|
@service('data-source/service') dataSource;
|
||||||
|
@service('dom') dom;
|
||||||
|
@service('logger') logger;
|
||||||
|
|
||||||
loading: 'eager',
|
@tracked isIntersecting = false;
|
||||||
|
@tracked data;
|
||||||
|
@tracked error;
|
||||||
|
|
||||||
isIntersecting: false,
|
constructor(owner, args) {
|
||||||
|
super(...arguments);
|
||||||
init: function() {
|
|
||||||
this._super(...arguments);
|
|
||||||
this._listeners = this.dom.listeners();
|
this._listeners = this.dom.listeners();
|
||||||
this._lazyListeners = this.dom.listeners();
|
this._lazyListeners = this.dom.listeners();
|
||||||
this.guid = this.dom.guid(this);
|
}
|
||||||
},
|
|
||||||
willDestroyElement: function() {
|
|
||||||
this.actions.close.apply(this);
|
|
||||||
this._listeners.remove();
|
|
||||||
this._lazyListeners.remove();
|
|
||||||
this._super(...arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
didInsertElement: function() {
|
get loading() {
|
||||||
this._super(...arguments);
|
return LOADING.includes(this.args.loading) ? this.args.loading : LOADING[0];
|
||||||
if (this.loading === 'lazy') {
|
}
|
||||||
|
|
||||||
|
get disabled() {
|
||||||
|
return typeof this.args.disabled !== 'undefined' ? this.args.disabled : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onchange(e) {
|
||||||
|
this.error = undefined;
|
||||||
|
this.data = e.data;
|
||||||
|
optional(this.args.onchange)(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
onerror(e) {
|
||||||
|
this.error = e.error || e;
|
||||||
|
optional(this.args.onerror)(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
connect($el) {
|
||||||
|
// $el is only a DOM node when loading = lazy
|
||||||
|
// otherwise its an array from the did-insert-helper
|
||||||
|
if (!Array.isArray($el)) {
|
||||||
this._lazyListeners.add(
|
this._lazyListeners.add(
|
||||||
this.dom.isInViewport(this.dom.element(`#${this.guid}`), inViewport => {
|
this.dom.isInViewport($el, inViewport => {
|
||||||
set(this, 'isIntersecting', inViewport);
|
this.isIntersecting = inViewport;
|
||||||
if (!this.isIntersecting) {
|
if (!this.isIntersecting) {
|
||||||
this.actions.close.bind(this)();
|
this.close();
|
||||||
} else {
|
} else {
|
||||||
this.actions.open.bind(this)();
|
this.open();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
},
|
|
||||||
didReceiveAttrs: function() {
|
|
||||||
this._super(...arguments);
|
|
||||||
if (this.loading === 'eager') {
|
|
||||||
this._lazyListeners.remove();
|
this._lazyListeners.remove();
|
||||||
|
this.open();
|
||||||
}
|
}
|
||||||
if (this.loading === 'eager' || this.isIntersecting) {
|
}
|
||||||
this.actions.open.apply(this, []);
|
|
||||||
}
|
@action
|
||||||
},
|
disconnect() {
|
||||||
actions: {
|
this.close();
|
||||||
// keep this argumentless
|
this._listeners.remove();
|
||||||
open: function() {
|
this._lazyListeners.remove();
|
||||||
// get a new source and replace the old one, cleaning up as we go
|
}
|
||||||
const source = replace(
|
|
||||||
this,
|
@action
|
||||||
'source',
|
attributeChanged([name, value]) {
|
||||||
this.data.open(this.src, this, this.open),
|
switch (name) {
|
||||||
(prev, source) => {
|
case 'src':
|
||||||
// Makes sure any previous source (if different) is ALWAYS closed
|
if (this.loading === 'eager' || this.isIntersecting) {
|
||||||
this.data.close(prev, this);
|
this.open();
|
||||||
}
|
}
|
||||||
);
|
break;
|
||||||
const error = err => {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep this argumentless
|
||||||
|
@action
|
||||||
|
open() {
|
||||||
|
const src = this.args.src;
|
||||||
|
// get a new source and replace the old one, cleaning up as we go
|
||||||
|
const source = replace(
|
||||||
|
this,
|
||||||
|
'source',
|
||||||
|
this.dataSource.open(src, this, this.open),
|
||||||
|
(prev, source) => {
|
||||||
|
// Makes sure any previous source (if different) is ALWAYS closed
|
||||||
|
this.dataSource.close(prev, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const error = err => {
|
||||||
|
try {
|
||||||
|
const error = get(err, 'error.errors.firstObject') || {};
|
||||||
|
if (get(error, 'status') !== '429') {
|
||||||
|
this.onerror(err);
|
||||||
|
}
|
||||||
|
this.logger.execute(err);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.execute(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// set up the listeners (which auto cleanup on component destruction)
|
||||||
|
const remove = this._listeners.add(this.source, {
|
||||||
|
message: e => {
|
||||||
try {
|
try {
|
||||||
const error = get(err, 'error.errors.firstObject');
|
this.onchange(e);
|
||||||
if (get(error || {}, 'status') !== '429') {
|
|
||||||
this.onerror(err);
|
|
||||||
}
|
|
||||||
this.logger.execute(err);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.execute(err);
|
error(err);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
// set up the listeners (which auto cleanup on component destruction)
|
error: e => {
|
||||||
const remove = this._listeners.add(this.source, {
|
error(e);
|
||||||
message: e => {
|
},
|
||||||
|
});
|
||||||
|
replace(this, '_remove', remove);
|
||||||
|
// dispatch the current data of the source if we have any
|
||||||
|
if (typeof source.getCurrentEvent === 'function') {
|
||||||
|
const currentEvent = source.getCurrentEvent();
|
||||||
|
if (currentEvent) {
|
||||||
|
let method;
|
||||||
|
if (typeof currentEvent.error !== 'undefined') {
|
||||||
|
method = 'onerror';
|
||||||
|
this.error = currentEvent.error;
|
||||||
|
} else {
|
||||||
|
this.error = undefined;
|
||||||
|
this.data = currentEvent.data;
|
||||||
|
method = 'onchange';
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid the re-render error
|
||||||
|
schedule('afterRender', () => {
|
||||||
try {
|
try {
|
||||||
this.onchange(e);
|
this[method](currentEvent);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error(err);
|
error(err);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
error: e => {
|
|
||||||
error(e);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
replace(this, '_remove', remove);
|
|
||||||
// dispatch the current data of the source if we have any
|
|
||||||
if (typeof source.getCurrentEvent === 'function') {
|
|
||||||
const currentEvent = source.getCurrentEvent();
|
|
||||||
if (currentEvent) {
|
|
||||||
schedule('afterRender', () => {
|
|
||||||
try {
|
|
||||||
this.onchange(currentEvent);
|
|
||||||
} catch (err) {
|
|
||||||
error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// keep this argumentless
|
}
|
||||||
close: function() {
|
|
||||||
if (typeof this.source !== 'undefined') {
|
// keep this argumentless
|
||||||
this.data.close(this.source, this);
|
@action
|
||||||
replace(this, '_remove', undefined);
|
close() {
|
||||||
set(this, 'source', undefined);
|
if (typeof this.source !== 'undefined') {
|
||||||
}
|
this.dataSource.close(this.source, this);
|
||||||
},
|
replace(this, '_remove', undefined);
|
||||||
},
|
this.source = undefined;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Service, { inject as service } from '@ember/service';
|
import Service, { inject as service } from '@ember/service';
|
||||||
import { proxy } from 'consul-ui/utils/dom/event-source';
|
import { proxy } from 'consul-ui/utils/dom/event-source';
|
||||||
|
import { schedule } from '@ember/runloop';
|
||||||
|
|
||||||
import MultiMap from 'mnemonist/multi-map';
|
import MultiMap from 'mnemonist/multi-map';
|
||||||
|
|
||||||
|
@ -37,14 +38,18 @@ export default class DataSourceService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
this._listeners.remove();
|
// the will-destroy helper will fire AFTER services have had willDestroy
|
||||||
sources.forEach(function(item) {
|
// called on them, schedule any destroying to fire after the final render
|
||||||
item.close();
|
schedule('afterRender', () => {
|
||||||
|
this._listeners.remove();
|
||||||
|
sources.forEach(function(item) {
|
||||||
|
item.close();
|
||||||
|
});
|
||||||
|
cache = null;
|
||||||
|
sources = null;
|
||||||
|
usage.clear();
|
||||||
|
usage = null;
|
||||||
});
|
});
|
||||||
cache = null;
|
|
||||||
sources = null;
|
|
||||||
usage.clear();
|
|
||||||
usage = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
source(cb, attrs) {
|
source(cb, attrs) {
|
||||||
|
|
|
@ -4,8 +4,9 @@ import { clearRender, render, waitUntil } from '@ember/test-helpers';
|
||||||
import hbs from 'htmlbars-inline-precompile';
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
import test from 'ember-sinon-qunit/test-support/test';
|
import test from 'ember-sinon-qunit/test-support/test';
|
||||||
import Service from '@ember/service';
|
import Service, { inject as service } from '@ember/service';
|
||||||
|
|
||||||
|
import DataSourceComponent from 'consul-ui/components/data-source/index';
|
||||||
import { BlockingEventSource as RealEventSource } from 'consul-ui/utils/dom/event-source';
|
import { BlockingEventSource as RealEventSource } from 'consul-ui/utils/dom/event-source';
|
||||||
|
|
||||||
const createFakeBlockingEventSource = function() {
|
const createFakeBlockingEventSource = function() {
|
||||||
|
@ -43,8 +44,9 @@ module('Integration | Component | data-source', function(hooks) {
|
||||||
const addEventListener = this.stub();
|
const addEventListener = this.stub();
|
||||||
const removeEventListener = this.stub();
|
const removeEventListener = this.stub();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const fakeService = Service.extend({
|
const fakeService = class extends Service {
|
||||||
open: function(uri, obj) {
|
close = close;
|
||||||
|
open(uri, obj) {
|
||||||
open(uri);
|
open(uri);
|
||||||
const source = new BlockingEventSource();
|
const source = new BlockingEventSource();
|
||||||
source.getCurrentEvent = function() {
|
source.getCurrentEvent = function() {
|
||||||
|
@ -53,17 +55,23 @@ module('Integration | Component | data-source', function(hooks) {
|
||||||
source.addEventListener = addEventListener;
|
source.addEventListener = addEventListener;
|
||||||
source.removeEventListener = removeEventListener;
|
source.removeEventListener = removeEventListener;
|
||||||
return source;
|
return source;
|
||||||
},
|
}
|
||||||
close: close,
|
};
|
||||||
});
|
|
||||||
this.owner.register('service:data-source/fake-service', fakeService);
|
this.owner.register('service:data-source/fake-service', fakeService);
|
||||||
this.owner.inject('component:data-source', 'data', 'service:data-source/fake-service');
|
this.owner.register(
|
||||||
|
'component:data-source',
|
||||||
|
class extends DataSourceComponent {
|
||||||
|
@service('data-source/fake-service') dataSource;
|
||||||
|
}
|
||||||
|
);
|
||||||
this.actions.change = data => {
|
this.actions.change = data => {
|
||||||
count++;
|
count++;
|
||||||
switch (count) {
|
switch (count) {
|
||||||
case 1:
|
case 1:
|
||||||
assert.equal(data, 'a', 'change was called first with "a"');
|
assert.equal(data, 'a', 'change was called first with "a"');
|
||||||
setTimeout(() => this.set('src', 'b'), 0);
|
setTimeout(() => {
|
||||||
|
this.set('src', 'b');
|
||||||
|
}, 0);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
assert.equal(data, 'b', 'change was called second with "b"');
|
assert.equal(data, 'b', 'change was called second with "b"');
|
||||||
|
@ -92,17 +100,22 @@ module('Integration | Component | data-source', function(hooks) {
|
||||||
const source = new RealEventSource();
|
const source = new RealEventSource();
|
||||||
const error = this.stub();
|
const error = this.stub();
|
||||||
const close = this.stub();
|
const close = this.stub();
|
||||||
const fakeService = Service.extend({
|
const fakeService = class extends Service {
|
||||||
open: function(uri, obj) {
|
close = close;
|
||||||
|
open(uri, obj) {
|
||||||
source.getCurrentEvent = function() {
|
source.getCurrentEvent = function() {
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
return source;
|
return source;
|
||||||
},
|
}
|
||||||
close: close,
|
};
|
||||||
});
|
|
||||||
this.owner.register('service:data-source/fake-service', fakeService);
|
this.owner.register('service:data-source/fake-service', fakeService);
|
||||||
this.owner.inject('component:data-source', 'data', 'service:data-source/fake-service');
|
this.owner.register(
|
||||||
|
'component:data-source',
|
||||||
|
class extends DataSourceComponent {
|
||||||
|
@service('data-source/fake-service') dataSource;
|
||||||
|
}
|
||||||
|
);
|
||||||
this.actions.change = data => {
|
this.actions.change = data => {
|
||||||
source.dispatchEvent({ type: 'error', error: {} });
|
source.dispatchEvent({ type: 'error', error: {} });
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue