Skip to content

Commit eb79fd4

Browse files
committed
feat: add tests for abstract view model store
1 parent 1b90366 commit eb79fd4

File tree

5 files changed

+138
-80
lines changed

5 files changed

+138
-80
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
3+
import { AbstractViewModelStore } from './abstract-view-model.store';
4+
import { TestViewModelImpl } from './abstract-view-model.test';
5+
import { AbstractViewModelParams } from './abstract-view-model.types';
6+
import {
7+
ViewModelCreateConfig,
8+
ViewModelGenerateIdConfig,
9+
} from './view-model.store.types';
10+
11+
export class TestViewModelStoreImpl extends AbstractViewModelStore<TestViewModelImpl> {
12+
spies = {
13+
generateViewModelId: vi.fn(),
14+
};
15+
16+
createViewModel<VM extends TestViewModelImpl>(
17+
config: ViewModelCreateConfig<VM>,
18+
): VM {
19+
const VM = config.VM;
20+
21+
const params: AbstractViewModelParams<VM['payload']> = {
22+
id: config.id,
23+
payload: config.payload,
24+
parentViewModelId: config.parentViewModelId,
25+
};
26+
27+
return new VM(params);
28+
}
29+
30+
generateViewModelId<VM extends TestViewModelImpl>(
31+
config: ViewModelGenerateIdConfig<VM>,
32+
): string {
33+
const result = super.generateViewModelId(config);
34+
this.spies.generateViewModelId(result, config);
35+
return result;
36+
}
37+
}
38+
39+
const createTestViewModelStore = () => {
40+
return new TestViewModelStoreImpl();
41+
};
42+
43+
describe('AbstractViewModelStore', () => {
44+
it('create instance', () => {
45+
const vmStore = createTestViewModelStore();
46+
expect(vmStore).toBeDefined();
47+
});
48+
49+
it('is able to attach view model', async () => {
50+
const vmStore = createTestViewModelStore();
51+
const vm = new TestViewModelImpl({ id: '1', payload: '1' });
52+
await vmStore.attach(vm);
53+
expect(vmStore.get('1')).toBe(vm);
54+
expect(vmStore.instanceAttachedCount.get('1')).toBe(1);
55+
});
56+
57+
it('is able to detach view model', async () => {
58+
const vmStore = createTestViewModelStore();
59+
const vm = new TestViewModelImpl({ id: '1', payload: '1' });
60+
await vmStore.attach(vm);
61+
await vmStore.detach('1');
62+
expect(vmStore.get('1')).toBe(null);
63+
expect(vmStore.instanceAttachedCount.get('1')).toBe(undefined);
64+
});
65+
});

src/view-model/abstract-view-model.store.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ import {
1919

2020
declare const process: { env: { NODE_ENV?: string } };
2121

22-
export abstract class AbstractViewModelStore implements ViewModelStore {
23-
viewModels = observable.map<string, ViewModel>();
22+
export abstract class AbstractViewModelStore<
23+
VMBase extends ViewModel<any> = ViewModel<any>,
24+
> implements ViewModelStore<VMBase>
25+
{
26+
viewModels = observable.map<string, VMBase>();
2427
viewModelsByClasses = observable.map<string, string[]>();
2528

2629
instanceAttachedCount = new Map<string, number>();
@@ -52,11 +55,11 @@ export abstract class AbstractViewModelStore implements ViewModelStore {
5255
);
5356
}
5457

55-
abstract createViewModel<VM extends ViewModel<any>>(
58+
abstract createViewModel<VM extends VMBase>(
5659
config: ViewModelCreateConfig<VM>,
5760
): VM;
5861

59-
generateViewModelId<VM extends ViewModel<any>>(
62+
generateViewModelId<VM extends VMBase>(
6063
config: ViewModelGenerateIdConfig<VM>,
6164
): string {
6265
if (config.id) {
@@ -79,9 +82,7 @@ export abstract class AbstractViewModelStore implements ViewModelStore {
7982
}
8083
}
8184

82-
getId<T extends ViewModel>(
83-
idOrClass: Maybe<string | Class<T>>,
84-
): string | null {
85+
getId<T extends VMBase>(idOrClass: Maybe<string | Class<T>>): string | null {
8586
if (!idOrClass) return null;
8687

8788
const id =
@@ -94,23 +95,23 @@ export abstract class AbstractViewModelStore implements ViewModelStore {
9495
return id;
9596
}
9697

97-
has<T extends ViewModel>(idOrClass: Maybe<string | Class<T>>): boolean {
98+
has<T extends VMBase>(idOrClass: Maybe<string | Class<T>>): boolean {
9899
const id = this.getId(idOrClass);
99100

100101
if (!id) return false;
101102

102103
return this.viewModels.has(id);
103104
}
104105

105-
get<T extends ViewModel>(idOrClass: Maybe<string | Class<T>>): T | null {
106+
get<T extends VMBase>(idOrClass: Maybe<string | Class<T>>): T | null {
106107
const id = this.getId(idOrClass);
107108

108109
if (!id) return null;
109110

110111
return (this.viewModels.get(id) as Maybe<T>) ?? null;
111112
}
112113

113-
async mount(model: ViewModel) {
114+
async mount(model: VMBase) {
114115
this.mountingViews.add(model.id);
115116

116117
await model.mount();
@@ -120,7 +121,7 @@ export abstract class AbstractViewModelStore implements ViewModelStore {
120121
});
121122
}
122123

123-
async unmount(model: ViewModel) {
124+
async unmount(model: VMBase) {
124125
this.unmountingViews.add(model.id);
125126

126127
await model.unmount();
@@ -130,7 +131,7 @@ export abstract class AbstractViewModelStore implements ViewModelStore {
130131
});
131132
}
132133

133-
async attach(model: ViewModel) {
134+
async attach(model: VMBase) {
134135
const attachedCount = this.instanceAttachedCount.get(model.id) ?? 0;
135136

136137
this.instanceAttachedCount.set(model.id, attachedCount + 1);

src/view-model/abstract-view-model.test.ts

Lines changed: 49 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,103 +5,102 @@ import { Maybe } from '../utils/types';
55
import { AbstractViewModel } from './abstract-view-model';
66
import { ViewModel } from './view-model';
77

8-
const createTestViewModel = ({
8+
export class TestViewModelImpl extends AbstractViewModel<any, any> {
9+
spies = {
10+
mount: vi.fn(),
11+
unmount: vi.fn(),
12+
payloadChanged: vi.fn(),
13+
didMount: vi.fn(),
14+
didUnmount: vi.fn(),
15+
};
16+
17+
protected getParentViewModel(
18+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
19+
parentViewModelId: Maybe<string>,
20+
): ViewModel<any, ViewModel<any, any>> | null {
21+
throw new Error('Method not implemented.');
22+
}
23+
24+
didMount(): void {
25+
this.spies.didMount();
26+
}
27+
28+
mount(): void {
29+
super.mount();
30+
this.spies.mount();
31+
}
32+
33+
unmount(): void {
34+
super.unmount();
35+
this.spies.unmount();
36+
}
37+
38+
payloadChanged(payload: any): void {
39+
this.spies.payloadChanged(payload);
40+
}
41+
42+
didUnmount(): void {
43+
this.spies.didUnmount();
44+
}
45+
}
46+
export const createTestViewModel = ({
947
id = '1',
1048
payload = {},
1149
}: {
1250
id?: string;
1351
payload?: any;
1452
} = {}) => {
15-
class ViewModelImpl extends AbstractViewModel<any> {
16-
spies = {
17-
mount: vi.fn(),
18-
unmount: vi.fn(),
19-
payloadChanged: vi.fn(),
20-
didMount: vi.fn(),
21-
didUnmount: vi.fn(),
22-
};
23-
24-
protected getParentViewModel(
25-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
26-
parentViewModelId: Maybe<string>,
27-
): ViewModel<any, ViewModel<any, any>> | null {
28-
throw new Error('Method not implemented.');
29-
}
30-
31-
didMount(): void {
32-
this.spies.didMount();
33-
}
34-
35-
mount(): void {
36-
super.mount();
37-
this.spies.mount();
38-
}
39-
40-
unmount(): void {
41-
super.unmount();
42-
this.spies.unmount();
43-
}
44-
45-
payloadChanged(payload: any): void {
46-
this.spies.payloadChanged(payload);
47-
}
48-
49-
didUnmount(): void {
50-
this.spies.didUnmount();
51-
}
52-
}
53-
54-
const vm = new ViewModelImpl({ id, payload });
53+
const vm = new TestViewModelImpl({ id, payload });
5554

5655
return vm;
5756
};
5857

5958
describe('AbstractViewModel', () => {
6059
it('create instance', () => {
61-
const vm = createTestViewModel();
60+
const vm = new TestViewModelImpl({ id: '1', payload: {} });
6261
expect(vm).toBeDefined();
6362
});
6463

6564
it('has id', () => {
66-
const vm = createTestViewModel({ id: '1' });
65+
const vm = new TestViewModelImpl({ id: '1', payload: {} });
6766
expect(vm.id).toBe('1');
6867
});
6968

7069
it('has payload', () => {
71-
const vm = createTestViewModel({ payload: { test: 1 } });
70+
const vm = new TestViewModelImpl({ id: '1', payload: { test: 1 } });
7271
expect(vm.payload).toEqual({ test: 1 });
7372
});
7473

7574
it('has isMounted', () => {
76-
const vm = createTestViewModel();
75+
const vm = new TestViewModelImpl({ id: '1', payload: {} });
7776
expect(vm.isMounted).toBe(false);
7877
});
7978

8079
it('has mount method', () => {
81-
const vm = createTestViewModel();
80+
const vm = new TestViewModelImpl({ id: '1', payload: {} });
8281
expect(vm.mount).toBeDefined();
8382
});
8483

8584
it('has unmount method', () => {
86-
const vm = createTestViewModel();
85+
const vm = new TestViewModelImpl({ id: '1', payload: {} });
8786
expect(vm.unmount).toBeDefined();
8887
});
8988

9089
it('has dispose', () => {
91-
const vm = createTestViewModel();
90+
const vm = new TestViewModelImpl({ id: '1', payload: {} });
9291
expect(vm.dispose).toBeDefined();
9392
});
9493

9594
it('mount should be called once', () => {
96-
const vm = createTestViewModel();
95+
const vm = new TestViewModelImpl({ id: '1', payload: {} });
9796

9897
vm.mount();
9998

10099
expect(vm.spies.mount).toHaveBeenCalledOnce();
101100
});
102101

103102
it('didMount should be called after mount', () => {
104-
const vm = createTestViewModel();
103+
const vm = new TestViewModelImpl({ id: '1', payload: {} });
105104

106105
vm.mount();
107106

src/view-model/abstract-view-model.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ export abstract class AbstractViewModel<
2525
this.id = params.id;
2626
this.payload = params.payload;
2727

28-
console.info('fff');
29-
3028
makeObservable(this, {
3129
isMounted: observable.ref,
3230
payload: observable.ref,

src/view-model/view-model.store.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import {
1212
* Interface representing a store for managing view models.
1313
* It extends the Disposable interface, allowing for cleanup of resources.
1414
*/
15-
export interface ViewModelStore extends Disposable {
15+
export interface ViewModelStore<VMBase extends ViewModel<any> = ViewModel<any>>
16+
extends Disposable {
1617
/**
1718
* Retrieves the ID of a view model based on a given ID or class type.
1819
* @param idOrClass - The ID or class type of the view model.
1920
* @returns The ID of the view model, or null if not found.
2021
*/
21-
getId<T extends ViewModel<any>>(
22-
idOrClass: Maybe<ViewModel['id'] | Class<T>>,
22+
getId<T extends VMBase>(
23+
idOrClass: Maybe<VMBase['id'] | Class<T>>,
2324
): string | null;
2425

2526
/**
@@ -32,55 +33,49 @@ export interface ViewModelStore extends Disposable {
3233
* @param idOrClass - The ID or class type of the view model.
3334
* @returns True if the instance exists, false otherwise.
3435
*/
35-
has<T extends ViewModel<any>>(
36-
idOrClass: Maybe<ViewModel['id'] | Class<T>>,
37-
): boolean;
36+
has<T extends VMBase>(idOrClass: Maybe<VMBase['id'] | Class<T>>): boolean;
3837

3938
/**
4039
* Retrieves a view model instance from the store.
4140
* @param idOrClass - The ID or class type of the view model.
4241
* @returns The view model instance, or null if not found.
4342
*/
44-
get<T extends ViewModel<any>>(
45-
idOrClass: Maybe<ViewModel['id'] | Class<T>>,
46-
): T | null;
43+
get<T extends VMBase>(idOrClass: Maybe<VMBase['id'] | Class<T>>): T | null;
4744

4845
/**
4946
* Attaches a view model to the store.
5047
* @param model - The view model to attach.
5148
* @returns A promise that resolves when the operation is complete.
5249
*/
53-
attach(model: ViewModel): Promise<void>;
50+
attach(model: VMBase): Promise<void>;
5451

5552
/**
5653
* Detaches a view model from the store using its ID.
5754
* @param id - The ID of the view model to detach.
5855
* @returns A promise that resolves when the operation is complete.
5956
*/
60-
detach(id: ViewModel['id']): Promise<void>;
57+
detach(id: VMBase['id']): Promise<void>;
6158

6259
/**
6360
* Determines if a view model is able to render based on its ID.
6461
* @param id - The ID of the view model.
6562
* @returns True if the view model can render, false otherwise.
6663
*/
67-
isAbleToRenderView(id: Maybe<ViewModel['id']>): boolean;
64+
isAbleToRenderView(id: Maybe<VMBase['id']>): boolean;
6865

6966
/**
7067
* Creates a new view model instance based on the provided configuration.
7168
* @param config - The configuration for creating the view model.
7269
* @returns The newly created view model instance.
7370
*/
74-
createViewModel<VM extends ViewModel<any>>(
75-
config: ViewModelCreateConfig<VM>,
76-
): VM;
71+
createViewModel<VM extends VMBase>(config: ViewModelCreateConfig<VM>): VM;
7772

7873
/**
7974
* Generates a unique ID for a view model based on the provided configuration.
8075
* @param config - The configuration for generating the ID.
8176
* @returns The generated unique ID.
8277
*/
83-
generateViewModelId<VM extends ViewModel<any>>(
78+
generateViewModelId<VM extends VMBase>(
8479
config: ViewModelGenerateIdConfig<VM>,
8580
): string;
8681
}

0 commit comments

Comments
 (0)