Skip to content

Commit

Permalink
refactor: simplify integration app and run Cypress SSR tests (#2017)
Browse files Browse the repository at this point in the history
This commit removes `@Select` decorator usages from the integration app.
This also updated the Cypress configuration to match `ssr` folder if the
`SSR` environment variable is provided. This is done because the `--integrationFolder`
argument is not available anymore and we should be using `e2e.specPattern` property.
The `integration/project.json` has been updated to run `serve` target with a development
configuration so recompilations are faster by default; its `build` target has been replaced.
The HMR plugin has been removed from the integration app because it's not being maintained
anymore and brings some issues.
with `build-app`.
  • Loading branch information
arturovt authored May 15, 2023
1 parent 1ba73a5 commit 9a3af36
Show file tree
Hide file tree
Showing 22 changed files with 107 additions and 158 deletions.
5 changes: 4 additions & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { defineConfig } from 'cypress';

const isSsr = process.env.SSR === 'true';

export default defineConfig({
video: false,
screenshotOnRunFailure: false,
Expand All @@ -13,6 +15,7 @@ export default defineConfig({
return require('./cypress/plugins/index.js')(on, config);
},
baseUrl: 'http://localhost:4200',
excludeSpecPattern: ['**/plugins/**.js', '**/tsconfig.json']
excludeSpecPattern: ['**/plugins/**.js', '**/tsconfig.json'],
specPattern: isSsr ? './cypress/ssr/*.ts' : './cypress/e2e/*.ts'
}
});
14 changes: 3 additions & 11 deletions cypress/ssr/ssr.spec.ts → cypress/ssr/ssr.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/// <reference types="cypress" />

describe('Server side rendering', () => {
const listUrl = 'http://localhost:4200/list';
const faviconUrl = 'http://localhost:4200/favicon.ico';
Expand All @@ -25,25 +23,19 @@ describe('Server side rendering', () => {

it('should serve statics and favicon.ico', () => {
// Arrange & act & assert
cy.request(faviconUrl)
.its('status')
.should('equal', 200);
cy.request(faviconUrl).its('status').should('equal', 200);
});

it('"ngOnInit todo" should exist', () => {
// Arrange & act & assert
cy.request(listUrl)
.its('body')
.should('include', 'ngOnInit todo');
cy.request(listUrl).its('body').should('include', 'ngOnInit todo');
});

getOrderedLifecycleHooks().forEach(hook => {
it(`should have '${hook}' lifecycle hook output visible`, () => {
// Arrange
// Act
cy.request(listUrl)
.its('body')
.should('include', hook);
cy.request(listUrl).its('body').should('include', hook);
});
});

Expand Down
2 changes: 1 addition & 1 deletion cypress/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"typeRoots": ["../node_modules/@types"],
"lib": ["es2017", "dom"]
},
"include": ["e2e/*.ts", "support/*.ts", "../node_modules/cypress"]
"include": ["e2e/*.ts", "ssr/*.ts", "support/*.ts", "../node_modules/cypress"]
}
12 changes: 1 addition & 11 deletions integration/app/app.browser.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { NgModule } from '@angular/core';
import { StateContext } from '@ngxs/store';
import { BrowserTransferStateModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
import { NgxsHmrLifeCycle, NgxsHmrSnapshot as Snapshot } from '@ngxs/hmr-plugin';

import { AppComponent } from '@integration/app.component';
import { AppModule } from '@integration/app.module';
Expand All @@ -18,12 +16,4 @@ import { TODOS_STORAGE_KEY } from '@integration/store/todos/todos.model';
NgxsStoragePluginModule.forRoot({ key: [TODOS_STORAGE_KEY] })
]
})
export class AppBrowserModule implements NgxsHmrLifeCycle<Snapshot> {
public hmrNgxsStoreOnInit(ctx: StateContext<Snapshot>, snapshot: Partial<Snapshot>) {
ctx.patchState(snapshot);
}

public hmrNgxsStoreBeforeOnDestroy(ctx: StateContext<Snapshot>): Partial<Snapshot> {
return ctx.getState();
}
}
export class AppBrowserModule {}
95 changes: 39 additions & 56 deletions integration/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import {
AbstractControl,
FormArray,
FormBuilder,
FormControl,
FormGroup
} from '@angular/forms';
import { FormArray, FormBuilder, FormControl } from '@angular/forms';
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Store } from '@ngxs/store';
import { Observable } from 'rxjs';

import { TodoState } from '@integration/store/todos/todo/todo.state';
Expand All @@ -20,73 +14,62 @@ import { Extras, Pizza, Todo } from '@integration/store/todos/todos.model';
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
public allExtras: Extras[];
public pizzaForm: FormGroup;
public greeting: string;
@Select(TodoState) public todos$: Observable<Todo[]>;
@Select(TodoState.pandas) public pandas$: Observable<Todo[]>;
@Select(TodosState.pizza) public pizza$: Observable<Pizza>;
@Select(TodosState.injected) public injected$: Observable<string>;
readonly allExtras: Extras[] = [
{ name: 'cheese', selected: false },
{ name: 'mushrooms', selected: false },
{ name: 'olives', selected: false }
];

constructor(private store: Store, private formBuilder: FormBuilder) {}
pizzaForm = this._fb.group({
toppings: [''],
crust: [{ value: 'thin', disabled: true }],
extras: this._fb.array(this._extrasControls)
});

public get extras(): AbstractControl[] {
return (<FormArray>this.pizzaForm.get('extras')).controls;
}
greeting: string;
todos$: Observable<Todo[]> = this._store.select(TodoState);
pandas$: Observable<Todo[]> = this._store.select(TodoState.getPandas);
pizza$: Observable<Pizza> = this._store.select(TodosState.getPizza);
injected$: Observable<string> = this._store.select(TodosState.getInjected);

constructor(private _store: Store, private _fb: FormBuilder) {}

private get extrasControls(): FormControl[] {
return this.allExtras.map((extra: Extras) => this.formBuilder.control(extra.selected));
get extras(): FormControl[] {
const extras = this.pizzaForm.get('extras') as FormArray;
return extras.controls as FormControl[];
}

private static getAllExtras(): Extras[] {
return [
{ name: 'cheese', selected: false },
{ name: 'mushrooms', selected: false },
{ name: 'olives', selected: false }
];
private get _extrasControls(): FormControl[] {
return this.allExtras.map((extra: Extras) => this._fb.control(extra.selected));
}

public ngOnInit(): void {
this.allExtras = AppComponent.getAllExtras();
this.pizzaForm = this.createPizzaForm();
this.addTodoByOnInit();
ngOnInit(): void {
const payload: Todo = 'ngOnInit todo';
const state: Todo[] = this._store.selectSnapshot(TodoState);
if (!state.includes(payload)) {
this._store.dispatch(new AddTodo(payload));
}
}

public addTodo(todo: Todo) {
this.store.dispatch(new AddTodo(todo));
addTodo(todo: Todo) {
this._store.dispatch(new AddTodo(todo));
}

public removeTodo(index: number) {
this.store.dispatch(new RemoveTodo(index)).subscribe(() => {
removeTodo(index: number) {
this._store.dispatch(new RemoveTodo(index)).subscribe(() => {
console.log('Removed!');
});
}

public onSubmit() {
onSubmit() {
this.pizzaForm.patchValue({ toppings: 'olives' });
}

public onPrefix() {
this.store.dispatch(new SetPrefix());
}

public onLoadData() {
this.store.dispatch(new LoadData());
onPrefix() {
this._store.dispatch(new SetPrefix());
}

private createPizzaForm(): FormGroup {
return this.formBuilder.group({
toppings: [''],
crust: [{ value: 'thin', disabled: true }],
extras: this.formBuilder.array(this.extrasControls)
});
}

private addTodoByOnInit() {
const payload: Todo = 'ngOnInit todo';
const state: Todo[] = this.store.selectSnapshot(TodoState);
if (!state.includes(payload)) {
this.store.dispatch(new AddTodo(payload));
}
onLoadData() {
this._store.dispatch(new LoadData());
}
}
10 changes: 5 additions & 5 deletions integration/app/counter/counter.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Store } from '@ngxs/store';
import { CounterStateChangeAction } from '@integration/counter/counter.actions';
import { CounterState, CounterStateModel } from '@integration/counter/counter.state';
import { Observable } from 'rxjs';
Expand All @@ -9,11 +9,11 @@ import { Observable } from 'rxjs';
templateUrl: './counter.component.html'
})
export class CounterComponent implements OnInit {
@Select(CounterState) public counter$: Observable<CounterStateModel>;
counter$: Observable<CounterStateModel> = this._store.select(CounterState);

constructor(private store: Store) {}
constructor(private _store: Store) {}

public ngOnInit() {
this.store.dispatch(new CounterStateChangeAction());
ngOnInit() {
this._store.dispatch(new CounterStateChangeAction());
}
}
6 changes: 4 additions & 2 deletions integration/app/detail/detail.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { Select } from '@ngxs/store';
import { Store } from '@ngxs/store';
import { Observable } from 'rxjs';

import { DetailState } from '@integration/detail/detail.state';
Expand All @@ -9,5 +9,7 @@ import { DetailState } from '@integration/detail/detail.state';
templateUrl: './detail.component.html'
})
export class DetailComponent {
@Select(DetailState) detail$: Observable<boolean>;
detail$: Observable<boolean> = this._store.select(DetailState);

constructor(private _store: Store) {}
}
4 changes: 2 additions & 2 deletions integration/app/list/list.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div>
{{ list$ | async }},
{{ foo | async }}
{{ hello$ | async }}
</div>

<div *ngIf="router$ | async as router">
animals were resolved {{ router.root.firstChild.firstChild.data.list }}
animals were resolved {{ router.root!.firstChild!.firstChild!.data.list }}
</div>
10 changes: 6 additions & 4 deletions integration/app/list/list.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { Select } from '@ngxs/store';
import { Store } from '@ngxs/store';
import { RouterState } from '@ngxs/router-plugin';
import { Observable } from 'rxjs';

Expand All @@ -10,7 +10,9 @@ import { ListState } from '@integration/list/list.state';
templateUrl: './list.component.html'
})
export class ListComponent {
@Select(ListState) public list$: Observable<string[]>;
@Select(ListState.hello) public foo: Observable<string>;
@Select(RouterState.state) public router$: Observable<RouterState>;
list$: Observable<string[]> = this._store.select(ListState);
hello$ = this._store.select(ListState.getHello);
router$ = this._store.select(RouterState.state);

constructor(private _store: Store) {}
}
2 changes: 1 addition & 1 deletion integration/app/list/list.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Resolve } from '@angular/router';

@Injectable()
export class ListResolver implements Resolve<string[]> {
public async resolve(): Promise<string[]> {
async resolve(): Promise<string[]> {
return ['zebras', 'pandas', 'lions', 'giraffes'];
}
}
6 changes: 3 additions & 3 deletions integration/app/list/list.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import { Injectable } from '@angular/core';
@Injectable()
export class ListState implements NgxsOnInit, NgxsAfterBootstrap {
@Selector()
public static hello(): string {
static getHello(): string {
return 'hello';
}

public ngxsOnInit({ setState, getState }: StateContext<string[]>): void {
ngxsOnInit({ setState, getState }: StateContext<string[]>): void {
setState([...getState(), 'NgxsOnInit lazy']);
}

public ngxsAfterBootstrap({ setState, getState }: StateContext<string[]>): void {
ngxsAfterBootstrap({ setState, getState }: StateContext<string[]>): void {
setState([...getState(), 'NgxsAfterBootstrap lazy']);
}
}
8 changes: 4 additions & 4 deletions integration/app/store/todos/todo/todo.actions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Todo } from '@integration/store/todos/todos.model';

export class AddTodo {
public static type = 'AddTodo';
constructor(public readonly payload: Todo) {}
static type = 'AddTodo';
constructor(readonly payload: Todo) {}
}

export class RemoveTodo {
public static type = 'RemoveTodo';
constructor(public readonly payload: number) {}
static type = 'RemoveTodo';
constructor(readonly payload: number) {}
}
10 changes: 5 additions & 5 deletions integration/app/store/todos/todo/todo.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ import { Injectable } from '@angular/core';
@Injectable()
export class TodoState implements NgxsOnInit, NgxsAfterBootstrap {
@Selector()
public static pandas(state: Todo[]): Todo[] {
static getPandas(state: Todo[]): Todo[] {
return state.filter(s => s.indexOf('panda') > -1);
}

public ngxsOnInit({ getState, setState }: StateContext<Todo[]>) {
ngxsOnInit({ getState, setState }: StateContext<Todo[]>) {
const state: Todo[] = getState();
const payload: Todo = 'NgxsOnInit todo';
if (!state.includes(payload)) {
setState([...state, payload]);
}
}

public ngxsAfterBootstrap({ getState, setState }: StateContext<Todo[]>): void {
ngxsAfterBootstrap({ getState, setState }: StateContext<Todo[]>): void {
const state: Todo[] = getState();
const payload: Todo = 'NgxsAfterBootstrap todo';
if (!state.includes(payload)) {
Expand All @@ -39,12 +39,12 @@ export class TodoState implements NgxsOnInit, NgxsAfterBootstrap {
}

@Action(AddTodo)
public addTodo({ setState }: StateContext<Todo[]>, { payload }: AddTodo) {
addTodo({ setState }: StateContext<Todo[]>, { payload }: AddTodo) {
setState(state => [...state, payload]);
}

@Action(RemoveTodo)
public removeTodo({ setState }: StateContext<Todo[]>, { payload }: RemoveTodo) {
removeTodo({ setState }: StateContext<Todo[]>, { payload }: RemoveTodo) {
setState(state => state.filter((_, i) => i !== payload));
}
}
4 changes: 2 additions & 2 deletions integration/app/store/todos/todos.actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export class SetPrefix {
public static type = 'SetPrefix';
static type = 'SetPrefix';
}

export class LoadData {
public static type = 'LoadData';
static type = 'LoadData';
}
Loading

0 comments on commit 9a3af36

Please sign in to comment.