Skip to content

Commit

Permalink
test(store): ensure that selectors are leaving NGXS execution strateg…
Browse files Browse the repository at this point in the history
…y and cause CD (#1825)
  • Loading branch information
arturovt authored Feb 23, 2022
1 parent 90fdd58 commit 5b116c1
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/store/internals/testing/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { NgxsTestBed } from './ngxs.setup';
export { NgxsTesting } from './symbol';
export { freshPlatform } from './fresh-platform';
export { skipConsoleLogging } from './skip-console-logging';
26 changes: 26 additions & 0 deletions packages/store/internals/testing/src/skip-console-logging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/// <reference types="jest" />

export function skipConsoleLogging<T extends (...args: any[]) => any>(fn: T): ReturnType<T> {
const consoleSpies = [
jest.spyOn(console, 'log').mockImplementation(() => {}),
jest.spyOn(console, 'warn').mockImplementation(() => {}),
jest.spyOn(console, 'error').mockImplementation(() => {}),
jest.spyOn(console, 'info').mockImplementation(() => {})
];
function restoreSpies() {
consoleSpies.forEach(spy => spy.mockRestore());
}
let restoreSpyAsync = false;
try {
const returnValue = fn();
if (returnValue instanceof Promise) {
restoreSpyAsync = true;
return returnValue.finally(() => restoreSpies()) as ReturnType<T>;
}
return returnValue;
} finally {
if (!restoreSpyAsync) {
restoreSpies();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Component, NgModule, ApplicationRef } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Action, NgxsModule, State, StateContext, Store } from '@ngxs/store';
import { freshPlatform, skipConsoleLogging } from '@ngxs/store/internals/testing';

describe('Selectors within templates causing ticks (https://github.com/ngxs/store/issues/933)', () => {
class SetCountries {
static readonly type = '[CountriesState] Set countries';
}

@State<string[]>({
name: 'countries',
defaults: []
})
class CountriesState {
@Action(SetCountries)
async setCountries(ctx: StateContext<string[]>) {
await Promise.resolve();
ctx.setState(['USA', 'Canada']);
}
}

const count = 10;

@Component({
selector: 'app-child',
template: `
{{ countries$ | async }}
`
})
class TestChildComponent {
countries$ = this.store.select(CountriesState);

constructor(private store: Store) {}
}

@Component({
selector: 'app-root',
template: `
<app-child *ngFor="let item of items"></app-child>
`
})
class TestComponent {
items = new Array(count);
}

@NgModule({
imports: [BrowserModule, NgxsModule.forRoot([CountriesState])],
declarations: [TestComponent, TestChildComponent],
bootstrap: [TestComponent]
})
class TestModule {}

it(
'should run change detection whenever the selector emits after asynchronous action has been completed',
freshPlatform(async () => {
// Arrange
const { injector } = await skipConsoleLogging(() =>
platformBrowserDynamic().bootstrapModule(TestModule)
);
const store = injector.get(Store);
const appRef = injector.get(ApplicationRef);
const spy = jest.spyOn(appRef, 'tick');

// Act
await store.dispatch(new SetCountries()).toPromise();

// Assert
try {
expect(spy.mock.calls.length).toBeGreaterThan(count);
} finally {
spy.mockRestore();
}
})
);
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"lib": ["es2017", "dom"],
"lib": ["es2017", "dom", "es2018.promise"],
"typeRoots": ["node_modules/@types"]
},
"exclude": ["@ngxs/**", "integrations/**", "cypress"]
Expand Down

0 comments on commit 5b116c1

Please sign in to comment.