@memberjunction/ng-search 5.24.0 → 5.25.0

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 (41) hide show
  1. package/dist/__tests__/search-types.test.js +6 -2
  2. package/dist/__tests__/search-types.test.js.map +1 -1
  3. package/dist/__tests__/search.service.test.js +25 -18
  4. package/dist/__tests__/search.service.test.js.map +1 -1
  5. package/dist/lib/module.d.ts +7 -4
  6. package/dist/lib/module.d.ts.map +1 -1
  7. package/dist/lib/module.js +19 -4
  8. package/dist/lib/module.js.map +1 -1
  9. package/dist/lib/search-composite.component.d.ts +79 -0
  10. package/dist/lib/search-composite.component.d.ts.map +1 -0
  11. package/dist/lib/search-composite.component.js +258 -0
  12. package/dist/lib/search-composite.component.js.map +1 -0
  13. package/dist/lib/search-filter.component.d.ts +14 -1
  14. package/dist/lib/search-filter.component.d.ts.map +1 -1
  15. package/dist/lib/search-filter.component.js +128 -26
  16. package/dist/lib/search-filter.component.js.map +1 -1
  17. package/dist/lib/search-input.component.d.ts +61 -0
  18. package/dist/lib/search-input.component.d.ts.map +1 -0
  19. package/dist/lib/search-input.component.js +192 -0
  20. package/dist/lib/search-input.component.js.map +1 -0
  21. package/dist/lib/search-overlay.component.js +2 -2
  22. package/dist/lib/search-results.component.d.ts +9 -5
  23. package/dist/lib/search-results.component.d.ts.map +1 -1
  24. package/dist/lib/search-results.component.js +117 -98
  25. package/dist/lib/search-results.component.js.map +1 -1
  26. package/dist/lib/search-suggest.component.d.ts +95 -0
  27. package/dist/lib/search-suggest.component.d.ts.map +1 -0
  28. package/dist/lib/search-suggest.component.js +491 -0
  29. package/dist/lib/search-suggest.component.js.map +1 -0
  30. package/dist/lib/search-types.d.ts +27 -1
  31. package/dist/lib/search-types.d.ts.map +1 -1
  32. package/dist/lib/search-types.js.map +1 -1
  33. package/dist/lib/search.service.d.ts +27 -5
  34. package/dist/lib/search.service.d.ts.map +1 -1
  35. package/dist/lib/search.service.js +221 -92
  36. package/dist/lib/search.service.js.map +1 -1
  37. package/dist/public-api.d.ts +3 -0
  38. package/dist/public-api.d.ts.map +1 -1
  39. package/dist/public-api.js +3 -0
  40. package/dist/public-api.js.map +1 -1
  41. package/package.json +6 -4
@@ -9,6 +9,7 @@ describe('Search Types', () => {
9
9
  EntityName: 'Contacts',
10
10
  RecordID: 'rec-123',
11
11
  SourceType: 'entity',
12
+ ResultType: 'entity-record',
12
13
  Score: 0.85,
13
14
  ScoreBreakdown: { Vector: 0.8, FullText: 0.9 },
14
15
  Tags: ['customer', 'active'],
@@ -19,6 +20,7 @@ describe('Search Types', () => {
19
20
  expect(item.Score).toBe(0.85);
20
21
  expect(item.Tags).toHaveLength(2);
21
22
  expect(item.SourceType).toBe('entity');
23
+ expect(item.ResultType).toBe('entity-record');
22
24
  });
23
25
  });
24
26
  describe('SearchResultGroup', () => {
@@ -73,7 +75,8 @@ describe('Search Types', () => {
73
75
  Filters: [],
74
76
  TotalCount: 42,
75
77
  ElapsedMs: 150,
76
- SourceCounts: { Vector: 20, FullText: 15, Entity: 7 }
78
+ SourceCounts: { Vector: 20, FullText: 15, Entity: 7, Storage: 0 },
79
+ Providers: []
77
80
  };
78
81
  expect(response.Success).toBe(true);
79
82
  expect(response.SourceCounts.Vector + response.SourceCounts.FullText + response.SourceCounts.Entity).toBe(42);
@@ -86,7 +89,8 @@ describe('Search Types', () => {
86
89
  Filters: [],
87
90
  TotalCount: 0,
88
91
  ElapsedMs: 0,
89
- SourceCounts: { Vector: 0, FullText: 0, Entity: 0 },
92
+ SourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 },
93
+ Providers: [],
90
94
  ErrorMessage: 'Search service unavailable'
91
95
  };
92
96
  expect(response.Success).toBe(false);
@@ -1 +1 @@
1
- {"version":3,"file":"search-types.test.js","sourceRoot":"","sources":["../../src/__tests__/search-types.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n SearchResultItem,\n SearchResultGroup,\n SearchFilter,\n SearchFilterOption,\n SearchRequest,\n SearchResponse,\n SearchResultSelectedEvent,\n SearchFilterChangeEvent,\n SearchExecutedEvent,\n ScoreBreakdown\n} from '../lib/search-types';\n\ndescribe('Search Types', () => {\n describe('SearchResultItem', () => {\n it('should be constructable with all required fields', () => {\n const item: SearchResultItem = {\n ID: 'test-1',\n Title: 'Test Result',\n Snippet: 'A snippet of matching text',\n EntityName: 'Contacts',\n RecordID: 'rec-123',\n SourceType: 'entity',\n Score: 0.85,\n ScoreBreakdown: { Vector: 0.8, FullText: 0.9 },\n Tags: ['customer', 'active'],\n SourceIcon: 'fa-solid fa-database',\n MatchedAt: new Date()\n };\n\n expect(item.ID).toBe('test-1');\n expect(item.Score).toBe(0.85);\n expect(item.Tags).toHaveLength(2);\n expect(item.SourceType).toBe('entity');\n });\n });\n\n describe('SearchResultGroup', () => {\n it('should group results by source type', () => {\n const group: SearchResultGroup = {\n Label: 'Entity Records',\n Icon: 'fa-solid fa-database',\n SourceType: 'entity',\n Results: [],\n TotalCount: 0\n };\n\n expect(group.SourceType).toBe('entity');\n expect(group.Results).toHaveLength(0);\n });\n });\n\n describe('SearchFilter', () => {\n it('should support multi-select filter options', () => {\n const filter: SearchFilter = {\n Category: 'Source Type',\n Options: [\n { Label: 'Entities', Value: 'entity', Count: 5, IsSelected: true },\n { Label: 'Files', Value: 'file', Count: 3, IsSelected: false }\n ],\n MultiSelect: true\n };\n\n expect(filter.MultiSelect).toBe(true);\n expect(filter.Options).toHaveLength(2);\n expect(filter.Options[0].IsSelected).toBe(true);\n });\n });\n\n describe('SearchRequest', () => {\n it('should support filter configuration', () => {\n const request: SearchRequest = {\n Query: 'test query',\n MaxResults: 25,\n ActiveFilters: {\n 'Source Type': ['entity', 'file'],\n 'Entity': ['Contacts']\n },\n IncludeSources: ['vector', 'fulltext']\n };\n\n expect(request.ActiveFilters['Source Type']).toHaveLength(2);\n expect(request.IncludeSources).not.toContain('entity');\n });\n });\n\n describe('SearchResponse', () => {\n it('should include source counts', () => {\n const response: SearchResponse = {\n Success: true,\n Results: [],\n Groups: [],\n Filters: [],\n TotalCount: 42,\n ElapsedMs: 150,\n SourceCounts: { Vector: 20, FullText: 15, Entity: 7 }\n };\n\n expect(response.Success).toBe(true);\n expect(response.SourceCounts.Vector + response.SourceCounts.FullText + response.SourceCounts.Entity).toBe(42);\n });\n\n it('should support error state', () => {\n const response: SearchResponse = {\n Success: false,\n Results: [],\n Groups: [],\n Filters: [],\n TotalCount: 0,\n ElapsedMs: 0,\n SourceCounts: { Vector: 0, FullText: 0, Entity: 0 },\n ErrorMessage: 'Search service unavailable'\n };\n\n expect(response.Success).toBe(false);\n expect(response.ErrorMessage).toBeDefined();\n });\n });\n\n describe('ScoreBreakdown', () => {\n it('should support partial score sources', () => {\n const breakdown: ScoreBreakdown = {\n Vector: 0.92\n };\n\n expect(breakdown.Vector).toBe(0.92);\n expect(breakdown.FullText).toBeUndefined();\n expect(breakdown.Entity).toBeUndefined();\n });\n });\n});\n"]}
1
+ {"version":3,"file":"search-types.test.js","sourceRoot":"","sources":["../../src/__tests__/search-types.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAc9C,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC1B,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC9B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YACxD,MAAM,IAAI,GAAqB;gBAC3B,EAAE,EAAE,QAAQ;gBACZ,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,4BAA4B;gBACrC,UAAU,EAAE,UAAU;gBACtB,QAAQ,EAAE,SAAS;gBACnB,UAAU,EAAE,QAAQ;gBACpB,UAAU,EAAE,eAAe;gBAC3B,KAAK,EAAE,IAAI;gBACX,cAAc,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE;gBAC9C,IAAI,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC;gBAC5B,UAAU,EAAE,sBAAsB;gBAClC,SAAS,EAAE,IAAI,IAAI,EAAE;aACxB,CAAC;YAEF,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC/B,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC3C,MAAM,KAAK,GAAsB;gBAC7B,KAAK,EAAE,gBAAgB;gBACvB,IAAI,EAAE,sBAAsB;gBAC5B,UAAU,EAAE,QAAQ;gBACpB,OAAO,EAAE,EAAE;gBACX,UAAU,EAAE,CAAC;aAChB,CAAC;YAEF,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YAClD,MAAM,MAAM,GAAiB;gBACzB,QAAQ,EAAE,aAAa;gBACvB,OAAO,EAAE;oBACL,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE;oBAClE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE;iBACjE;gBACD,WAAW,EAAE,IAAI;aACpB,CAAC;YAEF,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC3C,MAAM,OAAO,GAAkB;gBAC3B,KAAK,EAAE,YAAY;gBACnB,UAAU,EAAE,EAAE;gBACd,aAAa,EAAE;oBACX,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;oBACjC,QAAQ,EAAE,CAAC,UAAU,CAAC;iBACzB;gBACD,cAAc,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;aACzC,CAAC;YAEF,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACpC,MAAM,QAAQ,GAAmB;gBAC7B,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,EAAE;gBACV,OAAO,EAAE,EAAE;gBACX,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,GAAG;gBACd,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;gBACjE,SAAS,EAAE,EAAE;aAChB,CAAC;YAEF,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,GAAG,QAAQ,CAAC,YAAY,CAAC,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YAClC,MAAM,QAAQ,GAAmB;gBAC7B,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,EAAE;gBACV,OAAO,EAAE,EAAE;gBACX,UAAU,EAAE,CAAC;gBACb,SAAS,EAAE,CAAC;gBACZ,YAAY,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;gBAC/D,SAAS,EAAE,EAAE;gBACb,YAAY,EAAE,4BAA4B;aAC7C,CAAC;YAEF,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QAChD,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC5C,MAAM,SAAS,GAAmB;gBAC9B,MAAM,EAAE,IAAI;aACf,CAAC;YAEF,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,aAAa,EAAE,CAAC;YAC3C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,CAAC;QAC7C,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n SearchResultItem,\n SearchResultGroup,\n SearchFilter,\n SearchFilterOption,\n SearchRequest,\n SearchResponse,\n SearchResultSelectedEvent,\n SearchFilterChangeEvent,\n SearchExecutedEvent,\n ScoreBreakdown\n} from '../lib/search-types';\n\ndescribe('Search Types', () => {\n describe('SearchResultItem', () => {\n it('should be constructable with all required fields', () => {\n const item: SearchResultItem = {\n ID: 'test-1',\n Title: 'Test Result',\n Snippet: 'A snippet of matching text',\n EntityName: 'Contacts',\n RecordID: 'rec-123',\n SourceType: 'entity',\n ResultType: 'entity-record',\n Score: 0.85,\n ScoreBreakdown: { Vector: 0.8, FullText: 0.9 },\n Tags: ['customer', 'active'],\n SourceIcon: 'fa-solid fa-database',\n MatchedAt: new Date()\n };\n\n expect(item.ID).toBe('test-1');\n expect(item.Score).toBe(0.85);\n expect(item.Tags).toHaveLength(2);\n expect(item.SourceType).toBe('entity');\n expect(item.ResultType).toBe('entity-record');\n });\n });\n\n describe('SearchResultGroup', () => {\n it('should group results by source type', () => {\n const group: SearchResultGroup = {\n Label: 'Entity Records',\n Icon: 'fa-solid fa-database',\n SourceType: 'entity',\n Results: [],\n TotalCount: 0\n };\n\n expect(group.SourceType).toBe('entity');\n expect(group.Results).toHaveLength(0);\n });\n });\n\n describe('SearchFilter', () => {\n it('should support multi-select filter options', () => {\n const filter: SearchFilter = {\n Category: 'Source Type',\n Options: [\n { Label: 'Entities', Value: 'entity', Count: 5, IsSelected: true },\n { Label: 'Files', Value: 'file', Count: 3, IsSelected: false }\n ],\n MultiSelect: true\n };\n\n expect(filter.MultiSelect).toBe(true);\n expect(filter.Options).toHaveLength(2);\n expect(filter.Options[0].IsSelected).toBe(true);\n });\n });\n\n describe('SearchRequest', () => {\n it('should support filter configuration', () => {\n const request: SearchRequest = {\n Query: 'test query',\n MaxResults: 25,\n ActiveFilters: {\n 'Source Type': ['entity', 'file'],\n 'Entity': ['Contacts']\n },\n IncludeSources: ['vector', 'fulltext']\n };\n\n expect(request.ActiveFilters['Source Type']).toHaveLength(2);\n expect(request.IncludeSources).not.toContain('entity');\n });\n });\n\n describe('SearchResponse', () => {\n it('should include source counts', () => {\n const response: SearchResponse = {\n Success: true,\n Results: [],\n Groups: [],\n Filters: [],\n TotalCount: 42,\n ElapsedMs: 150,\n SourceCounts: { Vector: 20, FullText: 15, Entity: 7, Storage: 0 },\n Providers: []\n };\n\n expect(response.Success).toBe(true);\n expect(response.SourceCounts.Vector + response.SourceCounts.FullText + response.SourceCounts.Entity).toBe(42);\n });\n\n it('should support error state', () => {\n const response: SearchResponse = {\n Success: false,\n Results: [],\n Groups: [],\n Filters: [],\n TotalCount: 0,\n ElapsedMs: 0,\n SourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 },\n Providers: [],\n ErrorMessage: 'Search service unavailable'\n };\n\n expect(response.Success).toBe(false);\n expect(response.ErrorMessage).toBeDefined();\n });\n });\n\n describe('ScoreBreakdown', () => {\n it('should support partial score sources', () => {\n const breakdown: ScoreBreakdown = {\n Vector: 0.92\n };\n\n expect(breakdown.Vector).toBe(0.92);\n expect(breakdown.FullText).toBeUndefined();\n expect(breakdown.Entity).toBeUndefined();\n });\n });\n});\n"]}
@@ -1,19 +1,26 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest';
2
2
  import { Metadata } from '@memberjunction/core';
3
+ import { GraphQLDataProvider, GraphQLSearchClient } from '@memberjunction/graphql-dataprovider';
3
4
  import { SearchService } from '../lib/search.service';
4
- /** Installs a fake Metadata.Provider with a mock ExecuteGQL that returns empty success */
5
+ /**
6
+ * Installs a fake Metadata.Provider that passes the `instanceof GraphQLDataProvider` check
7
+ * and stubs GraphQLSearchClient.ExecuteSearch to return empty success.
8
+ */
5
9
  function installMockProvider() {
6
- const mockExecuteGQL = vi.fn().mockResolvedValue({
7
- SearchKnowledge: {
8
- Success: true,
9
- Results: [],
10
- TotalCount: 0,
11
- ElapsedMs: 1,
12
- SourceCounts: { Vector: 0, FullText: 0, Entity: 0 },
13
- }
10
+ const mockExecuteSearch = vi.fn().mockResolvedValue({
11
+ Success: true,
12
+ Results: [],
13
+ TotalCount: 0,
14
+ ElapsedMs: 1,
15
+ SourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 },
14
16
  });
15
- Metadata['Provider'] = { ExecuteGQL: mockExecuteGQL };
16
- return mockExecuteGQL;
17
+ // Create a mock that passes `instanceof GraphQLDataProvider`
18
+ const mockProvider = Object.create(GraphQLDataProvider.prototype);
19
+ Metadata['Provider'] = mockProvider;
20
+ // Stub GraphQLSearchClient.prototype so the service's `new GraphQLSearchClient(provider)` works
21
+ vi.spyOn(GraphQLSearchClient.prototype, 'ExecuteSearch').mockImplementation(mockExecuteSearch);
22
+ vi.spyOn(GraphQLSearchClient.prototype, 'PreviewSearch').mockImplementation(mockExecuteSearch);
23
+ return mockExecuteSearch;
17
24
  }
18
25
  describe('SearchService', () => {
19
26
  let service;
@@ -128,7 +135,7 @@ describe('SearchService', () => {
128
135
  expect(groups).toHaveLength(3);
129
136
  const entityGroup = groups.find(g => g.SourceType === 'entity');
130
137
  expect(entityGroup?.Results).toHaveLength(2);
131
- expect(entityGroup?.Label).toBe('Entity Records');
138
+ expect(entityGroup?.Label).toBe('Database');
132
139
  });
133
140
  it('should return empty array for empty input', () => {
134
141
  const groups = service.GroupResults([]);
@@ -143,7 +150,7 @@ describe('SearchService', () => {
143
150
  ];
144
151
  const filters = service.BuildFilters(results);
145
152
  expect(filters).toHaveLength(3);
146
- const sourceFilter = filters.find(f => f.Category === 'Source Type');
153
+ const sourceFilter = filters.find(f => f.Category === 'Source');
147
154
  expect(sourceFilter?.Options).toHaveLength(2);
148
155
  });
149
156
  it('should limit tags filter to 15 options', () => {
@@ -175,15 +182,14 @@ describe('SearchService', () => {
175
182
  const internalOption = tagFilter.Options.find(o => o.Value === 'internal');
176
183
  expect(internalOption.Count).toBe(1);
177
184
  });
178
- it('should produce an empty Tags filter when no results have tags', () => {
185
+ it('should omit Tags filter when no results have tags', () => {
179
186
  const results = [
180
187
  createMockResult('1', 'entity', 'Contacts'),
181
188
  createMockResult('2', 'entity', 'Companies'),
182
189
  ];
183
190
  const filters = service.BuildFilters(results);
184
191
  const tagFilter = filters.find(f => f.Category === 'Tags');
185
- expect(tagFilter).toBeDefined();
186
- expect(tagFilter.Options).toHaveLength(0);
192
+ expect(tagFilter).toBeUndefined(); // Empty categories are filtered out
187
193
  });
188
194
  it('should sort tags by count descending', () => {
189
195
  const results = [
@@ -273,7 +279,7 @@ describe('SearchService', () => {
273
279
  const groups = service.GroupResults(results);
274
280
  expect(groups).toHaveLength(2);
275
281
  const entityGroup = groups.find(g => g.SourceType === 'entity');
276
- expect(entityGroup?.Label).toBe('Entity Records');
282
+ expect(entityGroup?.Label).toBe('Database');
277
283
  expect(entityGroup?.TotalCount).toBe(2);
278
284
  const contentGroup = groups.find(g => g.SourceType === 'content-item');
279
285
  expect(contentGroup?.Label).toBe('Content Items');
@@ -305,7 +311,7 @@ describe('SearchService', () => {
305
311
  const groups = service.GroupResults(results);
306
312
  expect(groups).toHaveLength(2);
307
313
  const entityGroup = groups.find(g => g.SourceType === 'entity');
308
- expect(entityGroup?.Label).toBe('Entity Records');
314
+ expect(entityGroup?.Label).toBe('Database');
309
315
  const customGroup = groups.find(g => g.SourceType === 'custom-plugin');
310
316
  expect(customGroup?.Label).toBe('custom-plugin');
311
317
  expect(customGroup?.Icon).toBe('fa-solid fa-circle');
@@ -320,6 +326,7 @@ function createMockResult(id, sourceType, entityName, tags = []) {
320
326
  EntityName: entityName,
321
327
  RecordID: `rec-${id}`,
322
328
  SourceType: sourceType,
329
+ ResultType: sourceType === 'file' ? 'storage-file' : 'entity-record',
323
330
  Score: 0.85,
324
331
  ScoreBreakdown: { Vector: 0.8, FullText: 0.9 },
325
332
  Tags: tags,
@@ -1 +1 @@
1
- {"version":3,"file":"search.service.test.js","sourceRoot":"","sources":["../../src/__tests__/search.service.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { Metadata } from '@memberjunction/core';\nimport { SearchService, RecentSearch } from '../lib/search.service';\nimport {\n SearchRequest,\n SearchResponse,\n SearchResultItem,\n SearchResultGroup,\n SearchFilter\n} from '../lib/search-types';\n\n/** Installs a fake Metadata.Provider with a mock ExecuteGQL that returns empty success */\nfunction installMockProvider(): ReturnType<typeof vi.fn> {\n const mockExecuteGQL = vi.fn().mockResolvedValue({\n SearchKnowledge: {\n Success: true,\n Results: [],\n TotalCount: 0,\n ElapsedMs: 1,\n SourceCounts: { Vector: 0, FullText: 0, Entity: 0 },\n }\n });\n (Metadata as unknown as Record<string, unknown>)['Provider'] = { ExecuteGQL: mockExecuteGQL };\n return mockExecuteGQL;\n}\n\ndescribe('SearchService', () => {\n let service: SearchService;\n\n beforeEach(() => {\n service = new SearchService();\n installMockProvider();\n });\n\n describe('ExecuteSearch', () => {\n it('should set IsSearching to true during search', async () => {\n const values: boolean[] = [];\n service.IsSearching$.subscribe(val => values.push(val));\n\n await service.ExecuteSearch({\n Query: 'test query',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector', 'fulltext', 'entity']\n });\n\n // Should have gone true then false\n expect(values).toContain(true);\n expect(values[values.length - 1]).toBe(false);\n });\n\n it('should return empty results for empty query', async () => {\n const response = await service.ExecuteSearch({\n Query: ' ',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n expect(response.Success).toBe(true);\n expect(response.Results).toHaveLength(0);\n expect(response.TotalCount).toBe(0);\n });\n\n it('should update SearchResults$ observable', async () => {\n await service.ExecuteSearch({\n Query: 'test',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n const result = service.SearchResults$.value;\n expect(result).not.toBeNull();\n expect(result?.Success).toBe(true);\n });\n\n it('should add to recent searches', async () => {\n await service.ExecuteSearch({\n Query: 'test query',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n const recent = service.RecentSearches$.value;\n expect(recent.length).toBe(1);\n expect(recent[0].Query).toBe('test query');\n });\n\n it('should deduplicate recent searches', async () => {\n const request: SearchRequest = {\n Query: 'test',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n };\n\n await service.ExecuteSearch(request);\n await service.ExecuteSearch(request);\n\n const recent = service.RecentSearches$.value;\n expect(recent.length).toBe(1);\n });\n\n it('should limit recent searches to max 20', async () => {\n for (let i = 0; i < 25; i++) {\n await service.ExecuteSearch({\n Query: `query ${i}`,\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n }\n\n expect(service.RecentSearches$.value.length).toBeLessThanOrEqual(20);\n });\n });\n\n describe('ClearResults', () => {\n it('should set SearchResults$ to null', async () => {\n await service.ExecuteSearch({\n Query: 'test',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n service.ClearResults();\n expect(service.SearchResults$.value).toBeNull();\n });\n });\n\n describe('ClearRecentSearches', () => {\n it('should clear all recent searches', async () => {\n await service.ExecuteSearch({\n Query: 'test',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n service.ClearRecentSearches();\n expect(service.RecentSearches$.value).toHaveLength(0);\n });\n });\n\n describe('GroupResults', () => {\n it('should group results by source type', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts'),\n createMockResult('2', 'entity', 'Contacts'),\n createMockResult('3', 'file', 'Documents'),\n createMockResult('4', 'web-page', 'Pages'),\n ];\n\n const groups = service.GroupResults(results);\n\n expect(groups).toHaveLength(3);\n const entityGroup = groups.find(g => g.SourceType === 'entity');\n expect(entityGroup?.Results).toHaveLength(2);\n expect(entityGroup?.Label).toBe('Entity Records');\n });\n\n it('should return empty array for empty input', () => {\n const groups = service.GroupResults([]);\n expect(groups).toHaveLength(0);\n });\n });\n\n describe('BuildFilters', () => {\n it('should build source type, entity, and tag filters', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts', ['customer']),\n createMockResult('2', 'file', 'Documents', ['internal']),\n ];\n\n const filters = service.BuildFilters(results);\n\n expect(filters).toHaveLength(3);\n const sourceFilter = filters.find(f => f.Category === 'Source Type');\n expect(sourceFilter?.Options).toHaveLength(2);\n });\n\n it('should limit tags filter to 15 options', () => {\n const tags = Array.from({ length: 20 }, (_, i) => `tag${i}`);\n const results = tags.map((tag, i) =>\n createMockResult(String(i), 'entity', 'Test', [tag])\n );\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n expect(tagFilter!.Options.length).toBeLessThanOrEqual(15);\n });\n\n it('should generate correct tag filter options with counts and icon', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts', ['crm', 'active']),\n createMockResult('2', 'entity', 'Companies', ['crm']),\n createMockResult('3', 'file', 'Docs', ['internal']),\n ];\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n expect(tagFilter).toBeDefined();\n expect(tagFilter!.MultiSelect).toBe(true);\n\n // 'crm' appears in 2 results, should be sorted first\n const crmOption = tagFilter!.Options.find(o => o.Value === 'crm');\n expect(crmOption).toBeDefined();\n expect(crmOption!.Count).toBe(2);\n expect(crmOption!.Icon).toBe('fa-solid fa-tag');\n expect(crmOption!.IsSelected).toBe(false);\n\n // 'active' and 'internal' each appear once\n const activeOption = tagFilter!.Options.find(o => o.Value === 'active');\n expect(activeOption!.Count).toBe(1);\n const internalOption = tagFilter!.Options.find(o => o.Value === 'internal');\n expect(internalOption!.Count).toBe(1);\n });\n\n it('should produce an empty Tags filter when no results have tags', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts'),\n createMockResult('2', 'entity', 'Companies'),\n ];\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n expect(tagFilter).toBeDefined();\n expect(tagFilter!.Options).toHaveLength(0);\n });\n\n it('should sort tags by count descending', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'A', ['rare', 'common']),\n createMockResult('2', 'entity', 'B', ['common']),\n createMockResult('3', 'entity', 'C', ['common']),\n ];\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n expect(tagFilter!.Options[0].Value).toBe('common');\n expect(tagFilter!.Options[0].Count).toBe(3);\n expect(tagFilter!.Options[1].Value).toBe('rare');\n expect(tagFilter!.Options[1].Count).toBe(1);\n });\n });\n\n describe('GetSourceIcon', () => {\n it('should return correct icon for known types', () => {\n expect(service.GetSourceIcon('entity')).toContain('fa-database');\n expect(service.GetSourceIcon('file')).toContain('fa-file');\n expect(service.GetSourceIcon('web-page')).toContain('fa-globe');\n });\n\n it('should return fallback icon for unknown types', () => {\n expect(service.GetSourceIcon('unknown')).toContain('fa-circle');\n });\n });\n\n // ═══════════════════════════════════════════════════════════════\n // SR-3 Bug Fix: BuildFilters should preserve all options from full result set\n // ═══════════════════════════════════════════════════════════════\n describe('BuildFilters (SR-3: filter options from full result set)', () => {\n it('should include all entity names even when results are filtered', () => {\n // Full result set with 3 entities\n const fullResults: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts', ['crm']),\n createMockResult('2', 'entity', 'Companies', ['crm']),\n createMockResult('3', 'entity', 'Deals', ['sales']),\n ];\n\n // Build filters from FULL set (this is what the fix does)\n const filters = service.BuildFilters(fullResults);\n const entityFilter = filters.find(f => f.Category === 'Entity');\n\n // All 3 entities should appear in filter options\n expect(entityFilter?.Options).toHaveLength(3);\n expect(entityFilter?.Options.map(o => o.Value)).toContain('Contacts');\n expect(entityFilter?.Options.map(o => o.Value)).toContain('Companies');\n expect(entityFilter?.Options.map(o => o.Value)).toContain('Deals');\n });\n\n it('should count tags correctly across all results', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts', ['crm', 'sales']),\n createMockResult('2', 'entity', 'Companies', ['crm']),\n createMockResult('3', 'entity', 'Deals', ['sales', 'pipeline']),\n ];\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n\n // 'crm' appears in 2 results, 'sales' in 2, 'pipeline' in 1\n expect(tagFilter?.Options).toHaveLength(3);\n const crmOption = tagFilter?.Options.find(o => o.Value === 'crm');\n expect(crmOption?.Count).toBe(2);\n });\n\n it('should not lose filter options when building from subset vs full set', () => {\n const fullResults: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts'),\n createMockResult('2', 'entity', 'Companies'),\n createMockResult('3', 'file', 'Documents'),\n ];\n\n // Simulating filtering to only Contacts\n const filteredResults = fullResults.filter(r => r.EntityName === 'Contacts');\n\n // Building from filtered set loses Companies and Documents\n const filteredFilters = service.BuildFilters(filteredResults);\n const filteredEntityFilter = filteredFilters.find(f => f.Category === 'Entity');\n expect(filteredEntityFilter?.Options).toHaveLength(1); // Only Contacts\n\n // Building from full set preserves all options (the fix)\n const fullFilters = service.BuildFilters(fullResults);\n const fullEntityFilter = fullFilters.find(f => f.Category === 'Entity');\n expect(fullEntityFilter?.Options).toHaveLength(3); // Contacts + Companies + Documents\n });\n });\n\n // ═══════════════════════════════════════════════════════════════\n // SR-4 Bug Fix: GroupResults should sort within groups by score\n // ═══════════════════════════════════════════════════════════════\n describe('GroupResults (SR-4: grouping and sorting)', () => {\n it('should group results by source type with correct labels', () => {\n const results: SearchResultItem[] = [\n { ...createMockResult('1', 'entity', 'Contacts'), Score: 0.9 },\n { ...createMockResult('2', 'content-item', 'Items'), Score: 0.8 },\n { ...createMockResult('3', 'entity', 'Companies'), Score: 0.7 },\n ];\n\n const groups = service.GroupResults(results);\n expect(groups).toHaveLength(2);\n\n const entityGroup = groups.find(g => g.SourceType === 'entity');\n expect(entityGroup?.Label).toBe('Entity Records');\n expect(entityGroup?.TotalCount).toBe(2);\n\n const contentGroup = groups.find(g => g.SourceType === 'content-item');\n expect(contentGroup?.Label).toBe('Content Items');\n expect(contentGroup?.TotalCount).toBe(1);\n });\n\n it('should use fallback label for unknown source types', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Test'),\n ];\n // Hack source type to unknown\n (results[0] as unknown as Record<string, unknown>)['SourceType'] = 'custom-type';\n\n const groups = service.GroupResults(results);\n expect(groups[0].Label).toBe('custom-type'); // Falls back to raw type name\n });\n\n it('should use fallback icon for unknown source types', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Test'),\n ];\n (results[0] as unknown as Record<string, unknown>)['SourceType'] = 'unknown-source';\n\n const groups = service.GroupResults(results);\n expect(groups[0].Icon).toBe('fa-solid fa-circle');\n });\n\n it('should handle mixed known and unknown source types', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts'),\n createMockResult('2', 'entity', 'Companies'),\n ];\n (results[1] as unknown as Record<string, unknown>)['SourceType'] = 'custom-plugin';\n\n const groups = service.GroupResults(results);\n expect(groups).toHaveLength(2);\n\n const entityGroup = groups.find(g => g.SourceType === 'entity');\n expect(entityGroup?.Label).toBe('Entity Records');\n\n const customGroup = groups.find(g => g.SourceType === 'custom-plugin');\n expect(customGroup?.Label).toBe('custom-plugin');\n expect(customGroup?.Icon).toBe('fa-solid fa-circle');\n });\n });\n});\n\nfunction createMockResult(\n id: string,\n sourceType: 'entity' | 'content-item' | 'file' | 'web-page',\n entityName: string,\n tags: string[] = []\n): SearchResultItem {\n return {\n ID: id,\n Title: `Result ${id}`,\n Snippet: `Snippet for result ${id}`,\n EntityName: entityName,\n RecordID: `rec-${id}`,\n SourceType: sourceType,\n Score: 0.85,\n ScoreBreakdown: { Vector: 0.8, FullText: 0.9 },\n Tags: tags,\n SourceIcon: 'fa-solid fa-database',\n MatchedAt: new Date()\n };\n}\n"]}
1
+ {"version":3,"file":"search.service.test.js","sourceRoot":"","sources":["../../src/__tests__/search.service.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { Metadata } from '@memberjunction/core';\nimport { GraphQLDataProvider, GraphQLSearchClient } from '@memberjunction/graphql-dataprovider';\nimport { SearchService, RecentSearch } from '../lib/search.service';\nimport {\n SearchRequest,\n SearchResponse,\n SearchResultItem,\n SearchResultGroup,\n SearchFilter\n} from '../lib/search-types';\n\n/**\n * Installs a fake Metadata.Provider that passes the `instanceof GraphQLDataProvider` check\n * and stubs GraphQLSearchClient.ExecuteSearch to return empty success.\n */\nfunction installMockProvider(): ReturnType<typeof vi.fn> {\n const mockExecuteSearch = vi.fn().mockResolvedValue({\n Success: true,\n Results: [],\n TotalCount: 0,\n ElapsedMs: 1,\n SourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 },\n });\n\n // Create a mock that passes `instanceof GraphQLDataProvider`\n const mockProvider = Object.create(GraphQLDataProvider.prototype);\n (Metadata as unknown as Record<string, unknown>)['Provider'] = mockProvider;\n\n // Stub GraphQLSearchClient.prototype so the service's `new GraphQLSearchClient(provider)` works\n vi.spyOn(GraphQLSearchClient.prototype, 'ExecuteSearch').mockImplementation(mockExecuteSearch);\n vi.spyOn(GraphQLSearchClient.prototype, 'PreviewSearch').mockImplementation(mockExecuteSearch);\n\n return mockExecuteSearch;\n}\n\ndescribe('SearchService', () => {\n let service: SearchService;\n\n beforeEach(() => {\n service = new SearchService();\n installMockProvider();\n });\n\n describe('ExecuteSearch', () => {\n it('should set IsSearching to true during search', async () => {\n const values: boolean[] = [];\n service.IsSearching$.subscribe(val => values.push(val));\n\n await service.ExecuteSearch({\n Query: 'test query',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector', 'fulltext', 'entity']\n });\n\n // Should have gone true then false\n expect(values).toContain(true);\n expect(values[values.length - 1]).toBe(false);\n });\n\n it('should return empty results for empty query', async () => {\n const response = await service.ExecuteSearch({\n Query: ' ',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n expect(response.Success).toBe(true);\n expect(response.Results).toHaveLength(0);\n expect(response.TotalCount).toBe(0);\n });\n\n it('should update SearchResults$ observable', async () => {\n await service.ExecuteSearch({\n Query: 'test',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n const result = service.SearchResults$.value;\n expect(result).not.toBeNull();\n expect(result?.Success).toBe(true);\n });\n\n it('should add to recent searches', async () => {\n await service.ExecuteSearch({\n Query: 'test query',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n const recent = service.RecentSearches$.value;\n expect(recent.length).toBe(1);\n expect(recent[0].Query).toBe('test query');\n });\n\n it('should deduplicate recent searches', async () => {\n const request: SearchRequest = {\n Query: 'test',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n };\n\n await service.ExecuteSearch(request);\n await service.ExecuteSearch(request);\n\n const recent = service.RecentSearches$.value;\n expect(recent.length).toBe(1);\n });\n\n it('should limit recent searches to max 20', async () => {\n for (let i = 0; i < 25; i++) {\n await service.ExecuteSearch({\n Query: `query ${i}`,\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n }\n\n expect(service.RecentSearches$.value.length).toBeLessThanOrEqual(20);\n });\n });\n\n describe('ClearResults', () => {\n it('should set SearchResults$ to null', async () => {\n await service.ExecuteSearch({\n Query: 'test',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n service.ClearResults();\n expect(service.SearchResults$.value).toBeNull();\n });\n });\n\n describe('ClearRecentSearches', () => {\n it('should clear all recent searches', async () => {\n await service.ExecuteSearch({\n Query: 'test',\n MaxResults: 10,\n ActiveFilters: {},\n IncludeSources: ['vector']\n });\n\n service.ClearRecentSearches();\n expect(service.RecentSearches$.value).toHaveLength(0);\n });\n });\n\n describe('GroupResults', () => {\n it('should group results by source type', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts'),\n createMockResult('2', 'entity', 'Contacts'),\n createMockResult('3', 'file', 'Documents'),\n createMockResult('4', 'web-page', 'Pages'),\n ];\n\n const groups = service.GroupResults(results);\n\n expect(groups).toHaveLength(3);\n const entityGroup = groups.find(g => g.SourceType === 'entity');\n expect(entityGroup?.Results).toHaveLength(2);\n expect(entityGroup?.Label).toBe('Database');\n });\n\n it('should return empty array for empty input', () => {\n const groups = service.GroupResults([]);\n expect(groups).toHaveLength(0);\n });\n });\n\n describe('BuildFilters', () => {\n it('should build source type, entity, and tag filters', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts', ['customer']),\n createMockResult('2', 'file', 'Documents', ['internal']),\n ];\n\n const filters = service.BuildFilters(results);\n\n expect(filters).toHaveLength(3);\n const sourceFilter = filters.find(f => f.Category === 'Source');\n expect(sourceFilter?.Options).toHaveLength(2);\n });\n\n it('should limit tags filter to 15 options', () => {\n const tags = Array.from({ length: 20 }, (_, i) => `tag${i}`);\n const results = tags.map((tag, i) =>\n createMockResult(String(i), 'entity', 'Test', [tag])\n );\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n expect(tagFilter!.Options.length).toBeLessThanOrEqual(15);\n });\n\n it('should generate correct tag filter options with counts and icon', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts', ['crm', 'active']),\n createMockResult('2', 'entity', 'Companies', ['crm']),\n createMockResult('3', 'file', 'Docs', ['internal']),\n ];\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n expect(tagFilter).toBeDefined();\n expect(tagFilter!.MultiSelect).toBe(true);\n\n // 'crm' appears in 2 results, should be sorted first\n const crmOption = tagFilter!.Options.find(o => o.Value === 'crm');\n expect(crmOption).toBeDefined();\n expect(crmOption!.Count).toBe(2);\n expect(crmOption!.Icon).toBe('fa-solid fa-tag');\n expect(crmOption!.IsSelected).toBe(false);\n\n // 'active' and 'internal' each appear once\n const activeOption = tagFilter!.Options.find(o => o.Value === 'active');\n expect(activeOption!.Count).toBe(1);\n const internalOption = tagFilter!.Options.find(o => o.Value === 'internal');\n expect(internalOption!.Count).toBe(1);\n });\n\n it('should omit Tags filter when no results have tags', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts'),\n createMockResult('2', 'entity', 'Companies'),\n ];\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n expect(tagFilter).toBeUndefined(); // Empty categories are filtered out\n });\n\n it('should sort tags by count descending', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'A', ['rare', 'common']),\n createMockResult('2', 'entity', 'B', ['common']),\n createMockResult('3', 'entity', 'C', ['common']),\n ];\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n expect(tagFilter!.Options[0].Value).toBe('common');\n expect(tagFilter!.Options[0].Count).toBe(3);\n expect(tagFilter!.Options[1].Value).toBe('rare');\n expect(tagFilter!.Options[1].Count).toBe(1);\n });\n });\n\n describe('GetSourceIcon', () => {\n it('should return correct icon for known types', () => {\n expect(service.GetSourceIcon('entity')).toContain('fa-database');\n expect(service.GetSourceIcon('file')).toContain('fa-file');\n expect(service.GetSourceIcon('web-page')).toContain('fa-globe');\n });\n\n it('should return fallback icon for unknown types', () => {\n expect(service.GetSourceIcon('unknown')).toContain('fa-circle');\n });\n });\n\n // ═══════════════════════════════════════════════════════════════\n // SR-3 Bug Fix: BuildFilters should preserve all options from full result set\n // ═══════════════════════════════════════════════════════════════\n describe('BuildFilters (SR-3: filter options from full result set)', () => {\n it('should include all entity names even when results are filtered', () => {\n // Full result set with 3 entities\n const fullResults: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts', ['crm']),\n createMockResult('2', 'entity', 'Companies', ['crm']),\n createMockResult('3', 'entity', 'Deals', ['sales']),\n ];\n\n // Build filters from FULL set (this is what the fix does)\n const filters = service.BuildFilters(fullResults);\n const entityFilter = filters.find(f => f.Category === 'Entity');\n\n // All 3 entities should appear in filter options\n expect(entityFilter?.Options).toHaveLength(3);\n expect(entityFilter?.Options.map(o => o.Value)).toContain('Contacts');\n expect(entityFilter?.Options.map(o => o.Value)).toContain('Companies');\n expect(entityFilter?.Options.map(o => o.Value)).toContain('Deals');\n });\n\n it('should count tags correctly across all results', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts', ['crm', 'sales']),\n createMockResult('2', 'entity', 'Companies', ['crm']),\n createMockResult('3', 'entity', 'Deals', ['sales', 'pipeline']),\n ];\n\n const filters = service.BuildFilters(results);\n const tagFilter = filters.find(f => f.Category === 'Tags');\n\n // 'crm' appears in 2 results, 'sales' in 2, 'pipeline' in 1\n expect(tagFilter?.Options).toHaveLength(3);\n const crmOption = tagFilter?.Options.find(o => o.Value === 'crm');\n expect(crmOption?.Count).toBe(2);\n });\n\n it('should not lose filter options when building from subset vs full set', () => {\n const fullResults: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts'),\n createMockResult('2', 'entity', 'Companies'),\n createMockResult('3', 'file', 'Documents'),\n ];\n\n // Simulating filtering to only Contacts\n const filteredResults = fullResults.filter(r => r.EntityName === 'Contacts');\n\n // Building from filtered set loses Companies and Documents\n const filteredFilters = service.BuildFilters(filteredResults);\n const filteredEntityFilter = filteredFilters.find(f => f.Category === 'Entity');\n expect(filteredEntityFilter?.Options).toHaveLength(1); // Only Contacts\n\n // Building from full set preserves all options (the fix)\n const fullFilters = service.BuildFilters(fullResults);\n const fullEntityFilter = fullFilters.find(f => f.Category === 'Entity');\n expect(fullEntityFilter?.Options).toHaveLength(3); // Contacts + Companies + Documents\n });\n });\n\n // ═══════════════════════════════════════════════════════════════\n // SR-4 Bug Fix: GroupResults should sort within groups by score\n // ═══════════════════════════════════════════════════════════════\n describe('GroupResults (SR-4: grouping and sorting)', () => {\n it('should group results by source type with correct labels', () => {\n const results: SearchResultItem[] = [\n { ...createMockResult('1', 'entity', 'Contacts'), Score: 0.9 },\n { ...createMockResult('2', 'content-item', 'Items'), Score: 0.8 },\n { ...createMockResult('3', 'entity', 'Companies'), Score: 0.7 },\n ];\n\n const groups = service.GroupResults(results);\n expect(groups).toHaveLength(2);\n\n const entityGroup = groups.find(g => g.SourceType === 'entity');\n expect(entityGroup?.Label).toBe('Database');\n expect(entityGroup?.TotalCount).toBe(2);\n\n const contentGroup = groups.find(g => g.SourceType === 'content-item');\n expect(contentGroup?.Label).toBe('Content Items');\n expect(contentGroup?.TotalCount).toBe(1);\n });\n\n it('should use fallback label for unknown source types', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Test'),\n ];\n // Hack source type to unknown\n (results[0] as unknown as Record<string, unknown>)['SourceType'] = 'custom-type';\n\n const groups = service.GroupResults(results);\n expect(groups[0].Label).toBe('custom-type'); // Falls back to raw type name\n });\n\n it('should use fallback icon for unknown source types', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Test'),\n ];\n (results[0] as unknown as Record<string, unknown>)['SourceType'] = 'unknown-source';\n\n const groups = service.GroupResults(results);\n expect(groups[0].Icon).toBe('fa-solid fa-circle');\n });\n\n it('should handle mixed known and unknown source types', () => {\n const results: SearchResultItem[] = [\n createMockResult('1', 'entity', 'Contacts'),\n createMockResult('2', 'entity', 'Companies'),\n ];\n (results[1] as unknown as Record<string, unknown>)['SourceType'] = 'custom-plugin';\n\n const groups = service.GroupResults(results);\n expect(groups).toHaveLength(2);\n\n const entityGroup = groups.find(g => g.SourceType === 'entity');\n expect(entityGroup?.Label).toBe('Database');\n\n const customGroup = groups.find(g => g.SourceType === 'custom-plugin');\n expect(customGroup?.Label).toBe('custom-plugin');\n expect(customGroup?.Icon).toBe('fa-solid fa-circle');\n });\n });\n});\n\nfunction createMockResult(\n id: string,\n sourceType: 'entity' | 'content-item' | 'file' | 'web-page',\n entityName: string,\n tags: string[] = []\n): SearchResultItem {\n return {\n ID: id,\n Title: `Result ${id}`,\n Snippet: `Snippet for result ${id}`,\n EntityName: entityName,\n RecordID: `rec-${id}`,\n SourceType: sourceType,\n ResultType: sourceType === 'file' ? 'storage-file' : 'entity-record',\n Score: 0.85,\n ScoreBreakdown: { Vector: 0.8, FullText: 0.9 },\n Tags: tags,\n SourceIcon: 'fa-solid fa-database',\n MatchedAt: new Date()\n };\n}\n"]}
@@ -2,12 +2,15 @@ import * as i0 from "@angular/core";
2
2
  import * as i1 from "./search-overlay.component";
3
3
  import * as i2 from "./search-results.component";
4
4
  import * as i3 from "./search-filter.component";
5
- import * as i4 from "@angular/common";
6
- import * as i5 from "@angular/forms";
7
- import * as i6 from "@memberjunction/ng-shared-generic";
5
+ import * as i4 from "./search-input.component";
6
+ import * as i5 from "./search-suggest.component";
7
+ import * as i6 from "./search-composite.component";
8
+ import * as i7 from "@angular/common";
9
+ import * as i8 from "@angular/forms";
10
+ import * as i9 from "@memberjunction/ng-shared-generic";
8
11
  export declare class SearchModule {
9
12
  static ɵfac: i0.ɵɵFactoryDeclaration<SearchModule, never>;
10
- static ɵmod: i0.ɵɵNgModuleDeclaration<SearchModule, [typeof i1.SearchOverlayComponent, typeof i2.SearchResultsComponent, typeof i3.SearchFilterComponent], [typeof i4.CommonModule, typeof i5.FormsModule, typeof i6.SharedGenericModule], [typeof i1.SearchOverlayComponent, typeof i2.SearchResultsComponent, typeof i3.SearchFilterComponent]>;
13
+ static ɵmod: i0.ɵɵNgModuleDeclaration<SearchModule, [typeof i1.SearchOverlayComponent, typeof i2.SearchResultsComponent, typeof i3.SearchFilterComponent, typeof i4.SearchInputComponent, typeof i5.SearchSuggestComponent, typeof i6.SearchCompositeComponent], [typeof i7.CommonModule, typeof i8.FormsModule, typeof i9.SharedGenericModule], [typeof i1.SearchOverlayComponent, typeof i2.SearchResultsComponent, typeof i3.SearchFilterComponent, typeof i4.SearchInputComponent, typeof i5.SearchSuggestComponent, typeof i6.SearchCompositeComponent]>;
11
14
  static ɵinj: i0.ɵɵInjectorDeclaration<SearchModule>;
12
15
  }
13
16
  //# sourceMappingURL=module.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":";;;;;;;AASA,qBAiBa,YAAY;yCAAZ,YAAY;0CAAZ,YAAY;0CAAZ,YAAY;CAAI"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":";;;;;;;;;;AAYA,qBAuBa,YAAY;yCAAZ,YAAY;0CAAZ,YAAY;0CAAZ,YAAY;CAAI"}
@@ -5,6 +5,9 @@ import { SharedGenericModule } from '@memberjunction/ng-shared-generic';
5
5
  import { SearchOverlayComponent } from './search-overlay.component';
6
6
  import { SearchResultsComponent } from './search-results.component';
7
7
  import { SearchFilterComponent } from './search-filter.component';
8
+ import { SearchInputComponent } from './search-input.component';
9
+ import { SearchSuggestComponent } from './search-suggest.component';
10
+ import { SearchCompositeComponent } from './search-composite.component';
8
11
  import * as i0 from "@angular/core";
9
12
  export class SearchModule {
10
13
  static ɵfac = function SearchModule_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SearchModule)(); };
@@ -19,7 +22,10 @@ export class SearchModule {
19
22
  declarations: [
20
23
  SearchOverlayComponent,
21
24
  SearchResultsComponent,
22
- SearchFilterComponent
25
+ SearchFilterComponent,
26
+ SearchInputComponent,
27
+ SearchSuggestComponent,
28
+ SearchCompositeComponent
23
29
  ],
24
30
  imports: [
25
31
  CommonModule,
@@ -29,15 +35,24 @@ export class SearchModule {
29
35
  exports: [
30
36
  SearchOverlayComponent,
31
37
  SearchResultsComponent,
32
- SearchFilterComponent
38
+ SearchFilterComponent,
39
+ SearchInputComponent,
40
+ SearchSuggestComponent,
41
+ SearchCompositeComponent
33
42
  ]
34
43
  }]
35
44
  }], null, null); })();
36
45
  (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(SearchModule, { declarations: [SearchOverlayComponent,
37
46
  SearchResultsComponent,
38
- SearchFilterComponent], imports: [CommonModule,
47
+ SearchFilterComponent,
48
+ SearchInputComponent,
49
+ SearchSuggestComponent,
50
+ SearchCompositeComponent], imports: [CommonModule,
39
51
  FormsModule,
40
52
  SharedGenericModule], exports: [SearchOverlayComponent,
41
53
  SearchResultsComponent,
42
- SearchFilterComponent] }); })();
54
+ SearchFilterComponent,
55
+ SearchInputComponent,
56
+ SearchSuggestComponent,
57
+ SearchCompositeComponent] }); })();
43
58
  //# sourceMappingURL=module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAExE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;;AAmBlE,MAAM,OAAO,YAAY;sGAAZ,YAAY;4DAAZ,YAAY;gEAVjB,YAAY;YACZ,WAAW;YACX,mBAAmB;;iFAQd,YAAY;cAjBxB,QAAQ;eAAC;gBACN,YAAY,EAAE;oBACV,sBAAsB;oBACtB,sBAAsB;oBACtB,qBAAqB;iBACxB;gBACD,OAAO,EAAE;oBACL,YAAY;oBACZ,WAAW;oBACX,mBAAmB;iBACtB;gBACD,OAAO,EAAE;oBACL,sBAAsB;oBACtB,sBAAsB;oBACtB,qBAAqB;iBACxB;aACJ;;wFACY,YAAY,mBAfjB,sBAAsB;QACtB,sBAAsB;QACtB,qBAAqB,aAGrB,YAAY;QACZ,WAAW;QACX,mBAAmB,aAGnB,sBAAsB;QACtB,sBAAsB;QACtB,qBAAqB","sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { SharedGenericModule } from '@memberjunction/ng-shared-generic';\n\nimport { SearchOverlayComponent } from './search-overlay.component';\nimport { SearchResultsComponent } from './search-results.component';\nimport { SearchFilterComponent } from './search-filter.component';\n\n@NgModule({\n declarations: [\n SearchOverlayComponent,\n SearchResultsComponent,\n SearchFilterComponent\n ],\n imports: [\n CommonModule,\n FormsModule,\n SharedGenericModule\n ],\n exports: [\n SearchOverlayComponent,\n SearchResultsComponent,\n SearchFilterComponent\n ]\n})\nexport class SearchModule { }\n"]}
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAExE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;;AAyBxE,MAAM,OAAO,YAAY;sGAAZ,YAAY;4DAAZ,YAAY;gEAbjB,YAAY;YACZ,WAAW;YACX,mBAAmB;;iFAWd,YAAY;cAvBxB,QAAQ;eAAC;gBACN,YAAY,EAAE;oBACV,sBAAsB;oBACtB,sBAAsB;oBACtB,qBAAqB;oBACrB,oBAAoB;oBACpB,sBAAsB;oBACtB,wBAAwB;iBAC3B;gBACD,OAAO,EAAE;oBACL,YAAY;oBACZ,WAAW;oBACX,mBAAmB;iBACtB;gBACD,OAAO,EAAE;oBACL,sBAAsB;oBACtB,sBAAsB;oBACtB,qBAAqB;oBACrB,oBAAoB;oBACpB,sBAAsB;oBACtB,wBAAwB;iBAC3B;aACJ;;wFACY,YAAY,mBArBjB,sBAAsB;QACtB,sBAAsB;QACtB,qBAAqB;QACrB,oBAAoB;QACpB,sBAAsB;QACtB,wBAAwB,aAGxB,YAAY;QACZ,WAAW;QACX,mBAAmB,aAGnB,sBAAsB;QACtB,sBAAsB;QACtB,qBAAqB;QACrB,oBAAoB;QACpB,sBAAsB;QACtB,wBAAwB","sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { SharedGenericModule } from '@memberjunction/ng-shared-generic';\n\nimport { SearchOverlayComponent } from './search-overlay.component';\nimport { SearchResultsComponent } from './search-results.component';\nimport { SearchFilterComponent } from './search-filter.component';\nimport { SearchInputComponent } from './search-input.component';\nimport { SearchSuggestComponent } from './search-suggest.component';\nimport { SearchCompositeComponent } from './search-composite.component';\n\n@NgModule({\n declarations: [\n SearchOverlayComponent,\n SearchResultsComponent,\n SearchFilterComponent,\n SearchInputComponent,\n SearchSuggestComponent,\n SearchCompositeComponent\n ],\n imports: [\n CommonModule,\n FormsModule,\n SharedGenericModule\n ],\n exports: [\n SearchOverlayComponent,\n SearchResultsComponent,\n SearchFilterComponent,\n SearchInputComponent,\n SearchSuggestComponent,\n SearchCompositeComponent\n ]\n})\nexport class SearchModule { }\n"]}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @fileoverview Search Composite Component
3
+ *
4
+ * Drop-in widget combining SearchInputComponent + SearchSuggestComponent.
5
+ * Handles query debouncing, preview search calls, recent searches,
6
+ * keyboard navigation delegation, and click-outside-to-close.
7
+ */
8
+ import { EventEmitter, OnDestroy, OnInit } from '@angular/core';
9
+ import { SearchService, RecentSearch } from './search.service';
10
+ import { SearchInputComponent } from './search-input.component';
11
+ import { SearchSuggestComponent } from './search-suggest.component';
12
+ import { SearchResultItem } from './search-types';
13
+ import * as i0 from "@angular/core";
14
+ export declare class SearchCompositeComponent implements OnInit, OnDestroy {
15
+ private cdr;
16
+ private elementRef;
17
+ protected searchService: SearchService;
18
+ private destroy$;
19
+ searchInputRef: SearchInputComponent;
20
+ searchSuggestRef: SearchSuggestComponent;
21
+ /** Placeholder text for the search input */
22
+ Placeholder: string;
23
+ /** Whether to show keyboard shortcut hint on the input */
24
+ ShowShortcutHint: boolean;
25
+ /** Debounce time in milliseconds for query changes */
26
+ DebounceMs: number;
27
+ /** Minimum query length before preview results are fetched */
28
+ MinQueryLength: number;
29
+ /** Maximum number of preview results to show in the dropdown */
30
+ MaxPreviewResults: number;
31
+ /** Whether to enable live preview search on typing */
32
+ EnablePreview: boolean;
33
+ /** Whether to enable recent searches display */
34
+ EnableRecent: boolean;
35
+ /** Emitted when a result is selected from the preview dropdown */
36
+ ResultSelected: EventEmitter<SearchResultItem>;
37
+ /** Emitted when the user presses Enter or selects a recent search (full search) */
38
+ SearchSubmitted: EventEmitter<string>;
39
+ /** Emitted when the user clicks "See all N results" */
40
+ SeeAllRequested: EventEmitter<string>;
41
+ Query: string;
42
+ IsSuggestOpen: boolean;
43
+ /** The current min relevance percent from the suggest dropdown filter */
44
+ get MinRelevancePercent(): number;
45
+ PreviewResults: SearchResultItem[];
46
+ RecentSearches: RecentSearch[];
47
+ PreviewTotalCount: number;
48
+ IsPreviewLoading: boolean;
49
+ ngOnInit(): void;
50
+ ngOnDestroy(): void;
51
+ OnDocumentMousedown(event: MouseEvent): void;
52
+ OnHostKeydown(event: KeyboardEvent): void;
53
+ /** Handle debounced query changes from the input */
54
+ OnQueryChange(query: string): void;
55
+ /** Handle Enter press in the input */
56
+ OnQuerySubmit(query: string): void;
57
+ /** Handle input focus */
58
+ OnInputFocused(): void;
59
+ /** Handle Escape press in the input */
60
+ OnInputEscaped(): void;
61
+ /** Handle input cleared */
62
+ OnInputCleared(): void;
63
+ /** Handle result selection from suggest dropdown */
64
+ OnResultSelected(result: SearchResultItem): void;
65
+ /** Handle recent search selection */
66
+ OnRecentSelected(query: string): void;
67
+ /** Handle "See all" click */
68
+ OnSeeAllRequested(query: string): void;
69
+ /** Handle "Clear" recent searches */
70
+ OnClearRecentRequested(): void;
71
+ private openSuggest;
72
+ private closeSuggest;
73
+ private subscribeToRecentSearches;
74
+ private executePreviewSearch;
75
+ private clearPreviewResults;
76
+ static ɵfac: i0.ɵɵFactoryDeclaration<SearchCompositeComponent, never>;
77
+ static ɵcmp: i0.ɵɵComponentDeclaration<SearchCompositeComponent, "mj-search-composite", never, { "Placeholder": { "alias": "Placeholder"; "required": false; }; "ShowShortcutHint": { "alias": "ShowShortcutHint"; "required": false; }; "DebounceMs": { "alias": "DebounceMs"; "required": false; }; "MinQueryLength": { "alias": "MinQueryLength"; "required": false; }; "MaxPreviewResults": { "alias": "MaxPreviewResults"; "required": false; }; "EnablePreview": { "alias": "EnablePreview"; "required": false; }; "EnableRecent": { "alias": "EnableRecent"; "required": false; }; }, { "ResultSelected": "ResultSelected"; "SearchSubmitted": "SearchSubmitted"; "SeeAllRequested": "SeeAllRequested"; }, never, never, false, never>;
78
+ }
79
+ //# sourceMappingURL=search-composite.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-composite.component.d.ts","sourceRoot":"","sources":["../../src/lib/search-composite.component.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAGH,YAAY,EAGZ,SAAS,EACT,MAAM,EAKT,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;;AAElD,qBAMa,wBAAyB,YAAW,MAAM,EAAE,SAAS;IAC9D,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,UAAU,CAAsB;IACxC,SAAS,CAAC,aAAa,gBAAyB;IAChD,OAAO,CAAC,QAAQ,CAAuB;IAEb,cAAc,EAAG,oBAAoB,CAAC;IACpC,gBAAgB,EAAG,sBAAsB,CAAC;IAItE,4CAA4C;IACnC,WAAW,SAAe;IAEnC,0DAA0D;IACjD,gBAAgB,UAAQ;IAEjC,sDAAsD;IAC7C,UAAU,SAAO;IAE1B,8DAA8D;IACrD,cAAc,SAAK;IAE5B,gEAAgE;IACvD,iBAAiB,SAAK;IAE/B,sDAAsD;IAC7C,aAAa,UAAQ;IAE9B,gDAAgD;IACvC,YAAY,UAAQ;IAI7B,kEAAkE;IACxD,cAAc,iCAAwC;IAEhE,mFAAmF;IACzE,eAAe,uBAA8B;IAEvD,uDAAuD;IAC7C,eAAe,uBAA8B;IAIhD,KAAK,SAAM;IACX,aAAa,UAAS;IAE7B,yEAAyE;IACzE,IAAW,mBAAmB,IAAI,MAAM,CAEvC;IACM,cAAc,EAAE,gBAAgB,EAAE,CAAM;IACxC,cAAc,EAAE,YAAY,EAAE,CAAM;IACpC,iBAAiB,SAAK;IACtB,gBAAgB,UAAS;IAEhC,QAAQ,IAAI,IAAI;IAKhB,WAAW,IAAI,IAAI;IAQnB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAU5C,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAuBzC,oDAAoD;IAC7C,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAYzC,sCAAsC;IAC/B,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAazC,yBAAyB;IAClB,cAAc,IAAI,IAAI;IAI7B,uCAAuC;IAChC,cAAc,IAAI,IAAI;IAM7B,2BAA2B;IACpB,cAAc,IAAI,IAAI;IAM7B,oDAAoD;IAC7C,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAKvD,qCAAqC;IAC9B,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM5C,6BAA6B;IACtB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7C,qCAAqC;IAC9B,sBAAsB,IAAI,IAAI;IAMrC,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,yBAAyB;YASnB,oBAAoB;IAoBlC,OAAO,CAAC,mBAAmB;yCAxNlB,wBAAwB;2CAAxB,wBAAwB;CA6NpC"}