@savvagent/angular 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +484 -0
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/favicon.png +0 -0
  7. package/coverage/index.html +131 -0
  8. package/coverage/lcov-report/base.css +224 -0
  9. package/coverage/lcov-report/block-navigation.js +87 -0
  10. package/coverage/lcov-report/favicon.png +0 -0
  11. package/coverage/lcov-report/index.html +131 -0
  12. package/coverage/lcov-report/module.ts.html +289 -0
  13. package/coverage/lcov-report/prettify.css +1 -0
  14. package/coverage/lcov-report/prettify.js +2 -0
  15. package/coverage/lcov-report/service.ts.html +1846 -0
  16. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  17. package/coverage/lcov-report/sorter.js +210 -0
  18. package/coverage/lcov.info +242 -0
  19. package/coverage/module.ts.html +289 -0
  20. package/coverage/prettify.css +1 -0
  21. package/coverage/prettify.js +2 -0
  22. package/coverage/service.ts.html +1846 -0
  23. package/coverage/sort-arrow-sprite.png +0 -0
  24. package/coverage/sorter.js +210 -0
  25. package/dist/README.md +484 -0
  26. package/dist/esm2022/index.mjs +15 -0
  27. package/dist/esm2022/module.mjs +75 -0
  28. package/dist/esm2022/savvagent-angular.mjs +5 -0
  29. package/dist/esm2022/service.mjs +473 -0
  30. package/dist/fesm2022/savvagent-angular.mjs +563 -0
  31. package/dist/fesm2022/savvagent-angular.mjs.map +1 -0
  32. package/dist/index.d.ts +13 -0
  33. package/dist/module.d.ts +57 -0
  34. package/dist/service.d.ts +319 -0
  35. package/jest.config.js +40 -0
  36. package/ng-package.json +8 -0
  37. package/package.json +73 -0
  38. package/setup-jest.ts +2 -0
  39. package/src/index.spec.ts +144 -0
  40. package/src/index.ts +38 -0
  41. package/src/module.spec.ts +283 -0
  42. package/src/module.ts +68 -0
  43. package/src/service.spec.ts +945 -0
  44. package/src/service.ts +587 -0
  45. package/test-utils/angular-core-mock.ts +28 -0
  46. package/test-utils/angular-testing-mock.ts +87 -0
  47. package/tsconfig.json +33 -0
  48. package/tsconfig.spec.json +11 -0
@@ -0,0 +1,283 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { SavvagentModule } from './module';
3
+ import { SavvagentService, SAVVAGENT_CONFIG, SavvagentConfig } from './service';
4
+
5
+ describe('SavvagentModule', () => {
6
+ afterEach(() => {
7
+ TestBed.resetTestingModule();
8
+ });
9
+
10
+ describe('Module Configuration', () => {
11
+ it('should create module', () => {
12
+ const module = new SavvagentModule();
13
+ expect(module).toBeTruthy();
14
+ });
15
+
16
+ it('should provide SavvagentService by default', () => {
17
+ TestBed.configureTestingModule({
18
+ imports: [SavvagentModule],
19
+ });
20
+
21
+ const service = TestBed.inject(SavvagentService);
22
+ expect(service).toBeTruthy();
23
+ });
24
+ });
25
+
26
+ describe('forRoot Configuration', () => {
27
+ const testConfig: SavvagentConfig = {
28
+ config: {
29
+ apiKey: 'sdk_test_api_key',
30
+ baseUrl: 'https://api.test.com',
31
+ },
32
+ defaultContext: {
33
+ applicationId: 'test-app',
34
+ environment: 'test',
35
+ },
36
+ };
37
+
38
+ it('should return ModuleWithProviders', () => {
39
+ const moduleWithProviders = SavvagentModule.forRoot(testConfig);
40
+
41
+ expect(moduleWithProviders).toEqual({
42
+ ngModule: SavvagentModule,
43
+ providers: [
44
+ {
45
+ provide: SAVVAGENT_CONFIG,
46
+ useValue: testConfig,
47
+ },
48
+ SavvagentService,
49
+ ],
50
+ });
51
+ });
52
+
53
+ it('should provide SAVVAGENT_CONFIG token', () => {
54
+ TestBed.configureTestingModule({
55
+ imports: [SavvagentModule.forRoot(testConfig)],
56
+ });
57
+
58
+ const config = TestBed.inject(SAVVAGENT_CONFIG);
59
+ expect(config).toEqual(testConfig);
60
+ });
61
+
62
+ it('should initialize SavvagentService with config', () => {
63
+ TestBed.configureTestingModule({
64
+ imports: [SavvagentModule.forRoot(testConfig)],
65
+ });
66
+
67
+ const service = TestBed.inject(SavvagentService);
68
+ expect(service).toBeTruthy();
69
+ expect(service.isReady).toBe(true);
70
+ });
71
+
72
+ it('should work with minimal config', () => {
73
+ const minimalConfig: SavvagentConfig = {
74
+ config: {
75
+ apiKey: 'sdk_test_key',
76
+ },
77
+ };
78
+
79
+ TestBed.configureTestingModule({
80
+ imports: [SavvagentModule.forRoot(minimalConfig)],
81
+ });
82
+
83
+ const service = TestBed.inject(SavvagentService);
84
+ expect(service).toBeTruthy();
85
+ expect(service.isReady).toBe(true);
86
+ });
87
+
88
+ it('should work with full config including all context fields', () => {
89
+ const fullConfig: SavvagentConfig = {
90
+ config: {
91
+ apiKey: 'sdk_test_api_key',
92
+ baseUrl: 'https://api.test.com',
93
+ enableRealtime: true,
94
+ cacheTtl: 60000,
95
+ onError: (error) => console.error(error),
96
+ },
97
+ defaultContext: {
98
+ applicationId: 'test-app',
99
+ environment: 'production',
100
+ organizationId: 'org-123',
101
+ userId: 'user-456',
102
+ anonymousId: 'anon-789',
103
+ sessionId: 'session-abc',
104
+ language: 'en',
105
+ attributes: {
106
+ plan: 'pro',
107
+ region: 'us-west',
108
+ },
109
+ },
110
+ };
111
+
112
+ TestBed.configureTestingModule({
113
+ imports: [SavvagentModule.forRoot(fullConfig)],
114
+ });
115
+
116
+ const config = TestBed.inject(SAVVAGENT_CONFIG);
117
+ expect(config).toEqual(fullConfig);
118
+
119
+ const service = TestBed.inject(SavvagentService);
120
+ expect(service).toBeTruthy();
121
+ expect(service.isReady).toBe(true);
122
+ });
123
+ });
124
+
125
+ describe('Dependency Injection', () => {
126
+ it('should provide same service instance within module scope', () => {
127
+ TestBed.configureTestingModule({
128
+ imports: [
129
+ SavvagentModule.forRoot({
130
+ config: { apiKey: 'test' },
131
+ }),
132
+ ],
133
+ });
134
+
135
+ const service1 = TestBed.inject(SavvagentService);
136
+ const service2 = TestBed.inject(SavvagentService);
137
+
138
+ expect(service1).toBe(service2);
139
+ });
140
+
141
+ it('should work without forRoot (service providedIn: root)', () => {
142
+ TestBed.configureTestingModule({
143
+ imports: [SavvagentModule],
144
+ });
145
+
146
+ const service = TestBed.inject(SavvagentService);
147
+ expect(service).toBeTruthy();
148
+ // Service won't be initialized without config
149
+ expect(service.isReady).toBe(false);
150
+ });
151
+ });
152
+
153
+ describe('Multiple Imports', () => {
154
+ it('should handle multiple module imports', () => {
155
+ const config1: SavvagentConfig = {
156
+ config: { apiKey: 'key1' },
157
+ };
158
+ const config2: SavvagentConfig = {
159
+ config: { apiKey: 'key2' },
160
+ };
161
+
162
+ // The last imported config should win
163
+ TestBed.configureTestingModule({
164
+ imports: [
165
+ SavvagentModule.forRoot(config1),
166
+ SavvagentModule.forRoot(config2),
167
+ ],
168
+ });
169
+
170
+ const config = TestBed.inject(SAVVAGENT_CONFIG);
171
+ // Due to Angular's DI, the first provider typically wins,
172
+ // but this tests that the setup doesn't break
173
+ expect(config).toBeDefined();
174
+ });
175
+ });
176
+
177
+ describe('Integration Tests', () => {
178
+ it('should allow service to evaluate flags after module setup', async () => {
179
+ TestBed.configureTestingModule({
180
+ imports: [
181
+ SavvagentModule.forRoot({
182
+ config: { apiKey: 'sdk_test' },
183
+ defaultContext: {
184
+ applicationId: 'test-app',
185
+ },
186
+ }),
187
+ ],
188
+ });
189
+
190
+ const service = TestBed.inject(SavvagentService);
191
+ expect(service).toBeTruthy();
192
+ expect(service.isReady).toBe(true);
193
+ expect(service.flagClient).not.toBeNull();
194
+ });
195
+
196
+ it('should support standalone component pattern (Angular 14+)', () => {
197
+ // Simulate standalone component setup
198
+ const providers = SavvagentModule.forRoot({
199
+ config: { apiKey: 'sdk_test' },
200
+ }).providers || [];
201
+
202
+ TestBed.configureTestingModule({
203
+ providers,
204
+ });
205
+
206
+ const service = TestBed.inject(SavvagentService);
207
+ expect(service).toBeTruthy();
208
+ expect(service.isReady).toBe(true);
209
+ });
210
+ });
211
+
212
+ describe('Error Handling', () => {
213
+ it('should handle invalid config gracefully', () => {
214
+ const invalidConfig = {
215
+ config: {} as any, // Missing apiKey
216
+ };
217
+
218
+ expect(() => {
219
+ TestBed.configureTestingModule({
220
+ imports: [SavvagentModule.forRoot(invalidConfig)],
221
+ });
222
+ }).not.toThrow();
223
+ });
224
+
225
+ it('should handle null/undefined values in config', () => {
226
+ const configWithNulls: SavvagentConfig = {
227
+ config: {
228
+ apiKey: 'test',
229
+ baseUrl: undefined,
230
+ },
231
+ defaultContext: {
232
+ applicationId: undefined,
233
+ environment: undefined,
234
+ },
235
+ };
236
+
237
+ TestBed.configureTestingModule({
238
+ imports: [SavvagentModule.forRoot(configWithNulls)],
239
+ });
240
+
241
+ const service = TestBed.inject(SavvagentService);
242
+ expect(service).toBeTruthy();
243
+ });
244
+ });
245
+
246
+ describe('Type Safety', () => {
247
+ it('should enforce correct config structure', () => {
248
+ const validConfig: SavvagentConfig = {
249
+ config: {
250
+ apiKey: 'test_api_key',
251
+ },
252
+ defaultContext: {
253
+ userId: 'user-123',
254
+ },
255
+ };
256
+
257
+ const moduleWithProviders = SavvagentModule.forRoot(validConfig);
258
+ expect(moduleWithProviders.ngModule).toBe(SavvagentModule);
259
+ });
260
+
261
+ it('should accept all valid defaultContext properties', () => {
262
+ const config: SavvagentConfig = {
263
+ config: { apiKey: 'test' },
264
+ defaultContext: {
265
+ applicationId: 'app',
266
+ environment: 'prod',
267
+ organizationId: 'org',
268
+ userId: 'user',
269
+ anonymousId: 'anon',
270
+ sessionId: 'session',
271
+ language: 'en',
272
+ attributes: { key: 'value' },
273
+ },
274
+ };
275
+
276
+ expect(() => {
277
+ TestBed.configureTestingModule({
278
+ imports: [SavvagentModule.forRoot(config)],
279
+ });
280
+ }).not.toThrow();
281
+ });
282
+ });
283
+ });
package/src/module.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { NgModule, ModuleWithProviders } from '@angular/core';
2
+ import { SavvagentService, SavvagentConfig, SAVVAGENT_CONFIG } from './service';
3
+
4
+ /**
5
+ * Angular module for Savvagent feature flags.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // app.module.ts
10
+ * import { SavvagentModule } from '@savvagent/angular';
11
+ *
12
+ * @NgModule({
13
+ * imports: [
14
+ * SavvagentModule.forRoot({
15
+ * config: {
16
+ * apiKey: 'sdk_your_api_key',
17
+ * baseUrl: 'https://api.savvagent.com'
18
+ * },
19
+ * defaultContext: {
20
+ * applicationId: 'my-app',
21
+ * environment: 'production',
22
+ * userId: 'user-123'
23
+ * }
24
+ * })
25
+ * ]
26
+ * })
27
+ * export class AppModule {}
28
+ * ```
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * // For standalone components (Angular 14+)
33
+ * import { SavvagentModule } from '@savvagent/angular';
34
+ *
35
+ * bootstrapApplication(AppComponent, {
36
+ * providers: [
37
+ * importProvidersFrom(
38
+ * SavvagentModule.forRoot({
39
+ * config: { apiKey: 'sdk_...' }
40
+ * })
41
+ * )
42
+ * ]
43
+ * });
44
+ * ```
45
+ */
46
+ @NgModule({
47
+ providers: [SavvagentService]
48
+ })
49
+ export class SavvagentModule {
50
+ /**
51
+ * Configure the Savvagent module with API key and default context.
52
+ *
53
+ * @param savvagentConfig - Configuration including API key and optional default context
54
+ * @returns Module with providers
55
+ */
56
+ static forRoot(savvagentConfig: SavvagentConfig): ModuleWithProviders<SavvagentModule> {
57
+ return {
58
+ ngModule: SavvagentModule,
59
+ providers: [
60
+ {
61
+ provide: SAVVAGENT_CONFIG,
62
+ useValue: savvagentConfig
63
+ },
64
+ SavvagentService
65
+ ]
66
+ };
67
+ }
68
+ }