Skip to content

Commit

Permalink
Final Refactoring of naming changes
Browse files Browse the repository at this point in the history
  • Loading branch information
fancyDevelopment committed Sep 28, 2024
1 parent 669d6de commit f3e2d48
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 50 deletions.
4 changes: 2 additions & 2 deletions apps/playground/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
</div>
</div>
<div class="navbar-nav px-3">
@if(!appState.userInfo.isAvailable()) {
@if(!appState.userInfoState.isAvailable()) {
<div class="nav-item">
<button class="btn btn-secondary" (click)="logIn()">Login</button>
</div>
} @else {
<div class="nav-item">
<button class="btn btn-info text-nowrap" (click)="logOut()">
Logout <span class="badge text-bg-light">{{ appState.userInfo.resource.name() }}</span>
Logout <span class="badge text-bg-light">{{ appState.userInfo.name() }}</span>
</button>
</div>
}
Expand Down
2 changes: 1 addition & 1 deletion apps/playground/src/app/core/core.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ export const CORE_ROUTES: Routes = [{
}, {
path: 'home',
component: HomeComponent,
canActivate: [ () => whenTrue(inject(CoreState).homeVm.initiallyLoaded) ]
canActivate: [ () => whenTrue(inject(CoreState).homeVmState.initiallyLoaded) ]
}];
2 changes: 1 addition & 1 deletion apps/playground/src/app/core/home/home.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ import { CoreState } from '../core.state';
templateUrl: './home.component.html'
})
export class HomeComponent {
viewModel = inject(CoreState).homeVm.resource;
viewModel = inject(CoreState).homeVm;
}
2 changes: 1 addition & 1 deletion apps/playground/src/app/flight/flight.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ export const FLIHGT_ROUTES: Routes = [{
}, {
path: "create",
component: FlightCreateComponent,
canActivate: [() => whenTrue(inject(FlightState).flightCreateVm.initiallyLoaded)]
canActivate: [() => whenTrue(inject(FlightState).flightCreateVmState.initiallyLoaded)]
}];
2 changes: 1 addition & 1 deletion apps/playground/src/app/flight/flight.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const FlightState = signalStore(
store.connectUpdateFlightOperator(store.flightEditVm.flight.operator, 'update');
store.connectUpdateFlightPrice(store.flightEditVm.flight.price, 'update');
store.connectFlightCreateVm(store.flightSearchVm, 'flightCreateVm');
store.connectCreateFlight(store.flightCreateVm.resource.template, 'create');
store.connectCreateFlight(store.flightCreateVm.template, 'create');
}
})
);
2 changes: 1 addition & 1 deletion libs/ngrx-hateoas/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@angular-architects/ngrx-hateoas",
"version": "18.0.0-rc.2",
"version": "18.0.0-rc.3",
"peerDependencies": {
"@angular/common": "^18.0.0",
"@angular/core": "^18.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { signalStore, withHooks } from '@ngrx/signals';
import { withHypermediaResource } from './with-hypermedia-resource';
import { provideHateoas } from '../provide';
import { firstValueFrom, timer } from 'rxjs';
import { withHypermediaAction } from './with-hypermedia-action';

type TestModel = {
name: string,
_actions: {
doSomething?: {
href: string,
method: string
}
}
}

const initialTestModel: TestModel = {
name: 'initial',
_actions: {
}
};

const TestStore = signalStore(
{ providedIn: 'root' },
withHypermediaResource('testModel', initialTestModel),
withHypermediaAction('doSomething'),
withHooks({
onInit(store) {
store.connectDoSomething(store.testModel, 'doSomething')
},
})
);

describe('withHypermediaAction', () => {

let store: InstanceType<typeof TestStore>;
let httpTestingController: HttpTestingController;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [ provideHttpClient(), provideHttpClientTesting(), provideHateoas() ]
});
store = TestBed.inject(TestStore);
httpTestingController = TestBed.inject(HttpTestingController);
});

it('sets correct initial resource state', () => {
expect(store.doSomethingState.href()).toBe('');
expect(store.doSomethingState.method()).toBe('');
expect(store.doSomethingState.isAvailable()).toBeFalse();
expect(store.doSomethingState.isExecuting()).toBeFalse();
expect(store.doSomethingState.hasExecutedSuccessfully()).toBeFalse();
expect(store.doSomethingState.hasExecutedWithError()).toBeFalse();
expect(store.doSomethingState.hasError()).toBeFalse();
expect(store.doSomethingState.error()).toBeNull();
});

it('has correct resource methods', () => {
expect(store.doSomething).toBeDefined();
expect(store.connectDoSomething).toBeDefined();
});

it('does not execute action not available', async () => {
await store.doSomething();
httpTestingController.verify();
});

it('executes a successfull action after it is available', async () => {

const testModel = store.getTestModelAsPatchable();
testModel.name.patch('foobar');
testModel._actions.patch({ doSomething: { href: '/api/do-something', method: 'PUT' } });

await firstValueFrom(timer(0));

expect(store.doSomethingState.href()).toBe('/api/do-something');
expect(store.doSomethingState.method()).toBe('PUT');
expect(store.doSomethingState.isAvailable()).toBeTrue();
expect(store.doSomethingState.isExecuting()).toBeFalse();
expect(store.doSomethingState.hasExecutedSuccessfully()).toBeFalse();
expect(store.doSomethingState.hasExecutedWithError()).toBeFalse();
expect(store.doSomethingState.hasError()).toBeFalse();
expect(store.doSomethingState.error()).toBeNull();

const doSomethingPromise = store.doSomething();

expect(store.doSomethingState.href()).toBe('/api/do-something');
expect(store.doSomethingState.method()).toBe('PUT');
expect(store.doSomethingState.isAvailable()).toBeTrue();
expect(store.doSomethingState.isExecuting()).toBeTrue();
expect(store.doSomethingState.hasExecutedSuccessfully()).toBeFalse();
expect(store.doSomethingState.hasExecutedWithError()).toBeFalse();
expect(store.doSomethingState.hasError()).toBeFalse();
expect(store.doSomethingState.error()).toBeNull();

const actionRequest = httpTestingController.expectOne('/api/do-something');
httpTestingController.verify();

expect(actionRequest.request.method).toBe('PUT');
expect(actionRequest.request.body.name).toBe('foobar');

actionRequest.flush(204);

await doSomethingPromise;

expect(store.doSomethingState.href()).toBe('/api/do-something');
expect(store.doSomethingState.method()).toBe('PUT');
expect(store.doSomethingState.isAvailable()).toBeTrue();
expect(store.doSomethingState.isExecuting()).toBeFalse();
expect(store.doSomethingState.hasExecutedSuccessfully()).toBeTrue();
expect(store.doSomethingState.hasExecutedWithError()).toBeFalse();
expect(store.doSomethingState.hasError()).toBeFalse();
expect(store.doSomethingState.error()).toBeNull();
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function getState(store: unknown, stateKey: string): HypermediaActionStateProps
}

function updateState(stateKey: string, partialState: Partial<HypermediaActionStateProps>) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (state: any) => ({ [stateKey]: { ...state[stateKey], ...partialState } });
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function updateData<TResource>(dataKey: string, data: TResource) {
}

function updateState(stateKey: string, partialState: Partial<HypermediaResourceStateProps>) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (state: any) => ({ [stateKey]: { ...state[stateKey], ...partialState } });
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { Signal } from "@angular/core";
export function withInitialHypermediaResource<ResourceName extends string, TResource>(
resourceName: ResourceName, initialValue: TResource, url: string | (() => string)): SignalStoreFeature<
{
state: object; computed: Record<string, Signal<unknown>>; methods: Record<string, Function> },
state: object;
computed: Record<string, Signal<unknown>>;
methods: Record<string, Function>
},
{
state: HypermediaResourceStoreState<ResourceName, TResource>;
computed: Record<string, Signal<unknown>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ describe('withLinkedHypermediaResource', () => {
});

it('sets correct initial resource state', () => {
expect(store.testModel.url()).toBe('');
expect(store.testModel.initiallyLoaded()).toBeFalse();
expect(store.testModel.isAvailable()).toBeFalse();
expect(store.testModel.isLoading()).toBeFalse();
expect(store.testModel.resource()).toBe(initialTestModel);
expect(store.testModelState.url()).toBe('');
expect(store.testModelState.initiallyLoaded()).toBeFalse();
expect(store.testModelState.isAvailable()).toBeFalse();
expect(store.testModelState.isLoading()).toBeFalse();
expect(store.testModel()).toBe(initialTestModel);
});

it('has correct resource methods', () => {
Expand All @@ -83,20 +83,20 @@ describe('withLinkedHypermediaResource', () => {

await loadRootModel;

expect(store.testModel.url()).toBe('/api/test-model');
expect(store.testModel.initiallyLoaded()).toBeFalse();
expect(store.testModel.isLoading()).toBeTrue();
expect(store.testModel.isAvailable()).toBeTrue();
expect(store.testModelState.url()).toBe('/api/test-model');
expect(store.testModelState.initiallyLoaded()).toBeFalse();
expect(store.testModelState.isLoading()).toBeTrue();
expect(store.testModelState.isAvailable()).toBeTrue();

httpTestingController.expectOne('/api/test-model').flush(testModelFromLink);
httpTestingController.verify();

await firstValueFrom(timer(0));

expect(store.testModel.url()).toBe('/api/test-model');
expect(store.testModel.initiallyLoaded()).toBeTrue();
expect(store.testModel.isLoading()).toBeFalse();
expect(store.testModel.isAvailable()).toBeTrue();
expect(store.testModelState.url()).toBe('/api/test-model');
expect(store.testModelState.initiallyLoaded()).toBeTrue();
expect(store.testModelState.isLoading()).toBeFalse();
expect(store.testModelState.isAvailable()).toBeTrue();

loadRootModel = store.reloadRootModel();

Expand All @@ -105,10 +105,10 @@ describe('withLinkedHypermediaResource', () => {

await loadRootModel;

expect(store.testModel.url()).toBe('/api/test-model-changed');
expect(store.testModel.initiallyLoaded()).toBeTrue();
expect(store.testModel.isLoading()).toBeTrue();
expect(store.testModel.isAvailable()).toBeTrue();
expect(store.testModelState.url()).toBe('/api/test-model-changed');
expect(store.testModelState.initiallyLoaded()).toBeTrue();
expect(store.testModelState.isLoading()).toBeTrue();
expect(store.testModelState.isAvailable()).toBeTrue();

httpTestingController.expectOne('/api/test-model-changed');
httpTestingController.verify();
Expand Down Expand Up @@ -138,10 +138,10 @@ describe('withLinkedHypermediaResource', () => {

await firstValueFrom(timer(0));

expect(store.testModel.url()).toBe('/api/test-model');
expect(store.testModel.initiallyLoaded()).toBeTrue();
expect(store.testModel.isLoading()).toBeFalse();
expect(store.testModel.isAvailable()).toBeTrue();
expect(store.testModelState.url()).toBe('/api/test-model');
expect(store.testModelState.initiallyLoaded()).toBeTrue();
expect(store.testModelState.isLoading()).toBeFalse();
expect(store.testModelState.isAvailable()).toBeTrue();

loadRootModel = store.reloadRootModel();

Expand Down
Loading

0 comments on commit f3e2d48

Please sign in to comment.