Skip to content

Commit

Permalink
feat(navigation): add getTree method (#2793)
Browse files Browse the repository at this point in the history
  • Loading branch information
griest024 committed May 13, 2024
1 parent b4cc19b commit e300d70
Show file tree
Hide file tree
Showing 14 changed files with 298 additions and 45 deletions.
4 changes: 4 additions & 0 deletions libs/navigation/driver/in-memory/src/navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class DaffInMemoryNavigationService implements DaffNavigationServiceInter

constructor(private http: HttpClient) {}

getTree(): Observable<DaffNavigationTree> {
return this.http.get<DaffNavigationTree>(this.url);
}

get(navigationId: DaffNavigationTree['id']): Observable<DaffNavigationTree> {
return this.http.get<DaffNavigationTree>(this.url + navigationId);
}
Expand Down
6 changes: 6 additions & 0 deletions libs/navigation/driver/magento/src/config/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@ export interface MagentoNavigationDriverConfig {
* Defaults to 3.
*/
navigationTreeQueryDepth: number;

/**
* The UID of the root category.
* While this is optional, setting it will prevent an extra driver call during `getTree`.
*/
rootCategoryId?: string;
}
153 changes: 151 additions & 2 deletions libs/navigation/driver/magento/src/navigation.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { TestBed } from '@angular/core/testing';
import {
TestBed,
fakeAsync,
flush,
tick,
} from '@angular/core/testing';
import { InMemoryCache } from '@apollo/client/core';
import { addTypenameToDocument } from '@apollo/client/utilities';
import {
ApolloTestingModule,
ApolloTestingController,
APOLLO_TESTING_CACHE,
TestOperation,
} from 'apollo-angular/testing';

import { schema } from '@daffodil/driver/magento';
Expand All @@ -18,8 +24,12 @@ import { DaffNavigationTreeFactory } from '@daffodil/navigation/testing';

import { GetCategoryTreeResponse } from './models/public_api';
import { DaffMagentoNavigationService } from './navigation.service';
import {
MagentoNavgiationGetRootCategoryIdResponse,
magentoNavigationGetRootCategoryIdQuery,
} from './queries/get-root-category-id/public_api';

describe('Driver | Magento | Navigation | NavigationService', () => {
describe('@daffodil/navigation/driver/magento | DaffMagentoNavigationService', () => {
let navigationService: DaffMagentoNavigationService;
let navigationTreeFactory: DaffNavigationTreeFactory;
let controller: ApolloTestingController;
Expand Down Expand Up @@ -99,4 +109,143 @@ describe('Driver | Magento | Navigation | NavigationService', () => {
controller.verify();
});
});

describe('getTree', () => {
it('should return an observable single navigation', fakeAsync(() => {
const navigation = navigationTreeFactory.create();
const response: GetCategoryTreeResponse = {
categoryList: [{
__typename: 'CategoryTree',
uid: navigation.id,
url_path: navigation.id,
url_suffix: '.html',
name: navigation.name,
include_in_menu: true,
level: 0,
position: 1,
product_count: navigation.total_products,
children_count: navigation.children_count,
children: [],
breadcrumbs: [],
}],
};

navigationService.getTree().subscribe((result) => {
expect(result.id).toEqual(navigation.id);
expect(result.name).toEqual(navigation.name);
expect(result.url).toEqual(`/${navigation.id}.html`);
expect(result.total_products).toEqual(navigation.total_products);
expect(result.children_count).toEqual(navigation.children_count);
});

const rootIdOp: TestOperation<MagentoNavgiationGetRootCategoryIdResponse> = controller.expectOne(addTypenameToDocument(magentoNavigationGetRootCategoryIdQuery));
rootIdOp.flushData({
storeConfig: {
root_category_uid: navigation.id,
},
});

flush();
tick();

const op = controller.expectOne(addTypenameToDocument(daffMagentoGetCategoryTree(queryDepth)));

expect(op.operation.variables.filters).toEqual({ category_uid: { eq: navigation.id }});

op.flush({
data: response,
});
flush();
}));

afterEach(() => {
controller.verify();
});
});
});

describe('@daffodil/navigation/driver/magento | DaffMagentoNavigationService | with root category ID', () => {
let navigationService: DaffMagentoNavigationService;
let navigationTreeFactory: DaffNavigationTreeFactory;
let controller: ApolloTestingController;

const queryDepth = 1;
const categoryId = 'categoryId';

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
ApolloTestingModule,
],
providers: [
DaffMagentoNavigationService,
{ provide: DaffNavigationTransformer, useExisting: DaffMagentoNavigationTransformerService },
provideMagentoNavigationDriverConfig({
navigationTreeQueryDepth: queryDepth,
rootCategoryId: categoryId,
}),
{
provide: APOLLO_TESTING_CACHE,
useValue: new InMemoryCache({
addTypename: true,
possibleTypes: schema.possibleTypes,
}),
},
],
});

controller = TestBed.inject(ApolloTestingController);

navigationService = TestBed.inject(DaffMagentoNavigationService);
navigationTreeFactory = TestBed.inject(DaffNavigationTreeFactory);
});

it('should be created', () => {
expect(navigationService).toBeTruthy();
});

describe('getTree', () => {
it('should return an observable single navigation', done => {
const navigation = navigationTreeFactory.create({
id: categoryId,
});
const response: GetCategoryTreeResponse = {
categoryList: [{
__typename: 'CategoryTree',
uid: navigation.id,
url_path: navigation.id,
url_suffix: '.html',
name: navigation.name,
include_in_menu: true,
level: 0,
position: 1,
product_count: navigation.total_products,
children_count: navigation.children_count,
children: [],
breadcrumbs: [],
}],
};

navigationService.getTree().subscribe((result) => {
expect(result.id).toEqual(navigation.id);
expect(result.name).toEqual(navigation.name);
expect(result.url).toEqual(`/${navigation.id}.html`);
expect(result.total_products).toEqual(navigation.total_products);
expect(result.children_count).toEqual(navigation.children_count);
done();
});

const op = controller.expectOne(addTypenameToDocument(daffMagentoGetCategoryTree(queryDepth)));

expect(op.operation.variables.filters).toEqual({ category_uid: { eq: navigation.id }});

op.flush({
data: response,
});
});

afterEach(() => {
controller.verify();
});
});
});
25 changes: 23 additions & 2 deletions libs/navigation/driver/magento/src/navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import {
Inject,
} from '@angular/core';
import { Apollo } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import {
Observable,
of,
} from 'rxjs';
import {
map,
switchMap,
} from 'rxjs/operators';

import { DaffNavigationTree } from '@daffodil/navigation';
import {
Expand All @@ -19,6 +25,7 @@ import {
} from './config/public_api';
import { GetCategoryTreeResponse } from './models/get-category-tree-response';
import { daffMagentoGetCategoryTree } from './queries/get-category-tree';
import { magentoNavigationGetRootCategoryIdQuery } from './queries/get-root-category-id/public_api';

/**
* @inheritdoc
Expand All @@ -34,6 +41,20 @@ export class DaffMagentoNavigationService implements DaffNavigationServiceInterf
@Inject(MAGENTO_NAVIGATION_DRIVER_CONFIG) private config: MagentoNavigationDriverConfig,
) {}

getTree(): Observable<DaffNavigationTree> {
const rootCategoryId = this.config.rootCategoryId
? of(this.config.rootCategoryId)
: this.apollo.query({
query: magentoNavigationGetRootCategoryIdQuery,
}).pipe(
map(({ data }) => data.storeConfig.root_category_uid),
);

return rootCategoryId.pipe(
switchMap((id) => this.get(id)),
);
}

get(categoryId: string): Observable<DaffNavigationTree> {
return this.apollo.query<GetCategoryTreeResponse>({
query: daffMagentoGetCategoryTree(this.config.navigationTreeQueryDepth),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,40 @@ import { gql } from 'apollo-angular';
import { DocumentNode } from 'graphql';


/**
* A category tree fragment with no nested children.
*/
const categoryNodeFragment = `
uid
url_path
url_suffix
level
name
include_in_menu
breadcrumbs {
category_uid
category_name
category_level
category_url_path
}
position
product_count
`;
const CATEGORY_NODE_FRAGMENT_NAME = 'categoryNode';

/**
* Generates a category tree fragment with the specified number of nested child category trees.
*
* @param depth The maximum depth to which category children should be added to the fragment.
*/
//todo: use nested fragments when this bug is fixed: https://github.com/magento/magento2/issues/31086
export function getCategoryNodeFragment(depth: number = 3): DocumentNode {
const fragmentBody = new Array(depth).fill(null).reduce(acc => `
${categoryNodeFragment}
...${CATEGORY_NODE_FRAGMENT_NAME}
children_count
children {
${acc}
}
`, categoryNodeFragment);
`, `...${CATEGORY_NODE_FRAGMENT_NAME}`);

return gql`
fragment ${CATEGORY_NODE_FRAGMENT_NAME} on CategoryTree {
uid
url_path
url_suffix
level
name
include_in_menu
breadcrumbs {
category_uid
category_name
category_level
category_url_path
}
position
product_count
}
fragment recursiveCategoryNode on CategoryTree {
${fragmentBody}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './query';
export * from './response.type';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { gql } from 'apollo-angular';

import { MagentoNavgiationGetRootCategoryIdResponse } from './response.type';

export const MAGENTO_NAVIGATION_GET_ROOT_CATEGORY_ID_QUERY_NAME = 'MagentoNavigationGetRootCategoryId';

/**
* Generates a category tree query with the specified number of nested child category tree fragments.
*
* @param depth The maximum depth to which category children should be added to the fragment.
*/
export const magentoNavigationGetRootCategoryIdQuery = gql<MagentoNavgiationGetRootCategoryIdResponse, null>`
query ${MAGENTO_NAVIGATION_GET_ROOT_CATEGORY_ID_QUERY_NAME} {
storeConfig {
root_category_uid
}
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface MagentoNavgiationGetRootCategoryIdResponse {
__typename: string;
storeConfig: {
root_category_uid: string;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import { Observable } from 'rxjs';
import { DaffGenericNavigationTree } from '@daffodil/navigation';

export interface DaffNavigationServiceInterface<T extends DaffGenericNavigationTree<T>> {
get(categoryId: T['id']): Observable<T>;
/**
* Requests a specific navigation item by ID.
*/
get(id: T['id']): Observable<T>;

/**
* Requests the entire top-level navigation tree.
*/
getTree(): Observable<T>;
}

export const DaffNavigationDriver =
Expand Down
8 changes: 6 additions & 2 deletions libs/navigation/driver/testing/src/navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ import { DaffNavigationTreeFactory } from '@daffodil/navigation/testing';
providedIn: 'root',
})
export class DaffTestingNavigationService implements DaffNavigationServiceInterface<DaffNavigationTree> {

constructor(
private navigationTreeFactory: DaffNavigationTreeFactory) {}
private navigationTreeFactory: DaffNavigationTreeFactory,
) {}

getTree(): Observable<DaffNavigationTree> {
return of(this.navigationTreeFactory.create());
}

get(navigationTreeId: string): Observable<DaffNavigationTree> {
return of(this.navigationTreeFactory.create());
Expand Down
2 changes: 1 addition & 1 deletion libs/navigation/state/src/actions/navigation.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export enum DaffNavigationActionTypes {
export class DaffNavigationLoad implements Action {
readonly type = DaffNavigationActionTypes.NavigationLoadAction;

constructor(public payload: string) { }
constructor(public payload?: string) { }
}

export class DaffNavigationLoadSuccess<T extends DaffGenericNavigationTree<T>> implements Action {
Expand Down
Loading

0 comments on commit e300d70

Please sign in to comment.