facet.e2e-spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. import { pick } from '@vendure/common/lib/pick';
  2. import { createTestEnvironment } from '@vendure/testing';
  3. import gql from 'graphql-tag';
  4. import path from 'path';
  5. import { initialData } from '../../../e2e-common/e2e-initial-data';
  6. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  7. import { FACET_VALUE_FRAGMENT, FACET_WITH_VALUES_FRAGMENT } from './graphql/fragments';
  8. import {
  9. CreateFacet,
  10. CreateFacetValues,
  11. DeleteFacet,
  12. DeleteFacetValues,
  13. DeletionResult,
  14. FacetWithValues,
  15. GetFacetList,
  16. GetFacetWithValues,
  17. GetProductListWithVariants,
  18. GetProductWithVariants,
  19. LanguageCode,
  20. UpdateFacet,
  21. UpdateFacetValues,
  22. UpdateProduct,
  23. UpdateProductVariants,
  24. } from './graphql/generated-e2e-admin-types';
  25. import {
  26. CREATE_FACET,
  27. GET_FACET_LIST,
  28. GET_PRODUCT_WITH_VARIANTS,
  29. UPDATE_FACET,
  30. UPDATE_PRODUCT,
  31. UPDATE_PRODUCT_VARIANTS,
  32. } from './graphql/shared-definitions';
  33. // tslint:disable:no-non-null-assertion
  34. describe('Facet resolver', () => {
  35. const { server, adminClient } = createTestEnvironment(testConfig);
  36. let brandFacet: FacetWithValues.Fragment;
  37. let speakerTypeFacet: FacetWithValues.Fragment;
  38. beforeAll(async () => {
  39. await server.init({
  40. initialData,
  41. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  42. customerCount: 1,
  43. });
  44. await adminClient.asSuperAdmin();
  45. }, TEST_SETUP_TIMEOUT_MS);
  46. afterAll(async () => {
  47. await server.destroy();
  48. });
  49. it('createFacet', async () => {
  50. const result = await adminClient.query<CreateFacet.Mutation, CreateFacet.Variables>(CREATE_FACET, {
  51. input: {
  52. isPrivate: false,
  53. code: 'speaker-type',
  54. translations: [{ languageCode: LanguageCode.en, name: 'Speaker Type' }],
  55. values: [
  56. {
  57. code: 'portable',
  58. translations: [{ languageCode: LanguageCode.en, name: 'Portable' }],
  59. },
  60. ],
  61. },
  62. });
  63. speakerTypeFacet = result.createFacet;
  64. expect(speakerTypeFacet).toMatchSnapshot();
  65. });
  66. it('updateFacet', async () => {
  67. const result = await adminClient.query<UpdateFacet.Mutation, UpdateFacet.Variables>(UPDATE_FACET, {
  68. input: {
  69. id: speakerTypeFacet.id,
  70. translations: [{ languageCode: LanguageCode.en, name: 'Speaker Category' }],
  71. },
  72. });
  73. expect(result.updateFacet.name).toBe('Speaker Category');
  74. });
  75. it('createFacetValues', async () => {
  76. const { createFacetValues } = await adminClient.query<
  77. CreateFacetValues.Mutation,
  78. CreateFacetValues.Variables
  79. >(CREATE_FACET_VALUES, {
  80. input: [
  81. {
  82. facetId: speakerTypeFacet.id,
  83. code: 'pc',
  84. translations: [{ languageCode: LanguageCode.en, name: 'PC Speakers' }],
  85. },
  86. {
  87. facetId: speakerTypeFacet.id,
  88. code: 'hi-fi',
  89. translations: [{ languageCode: LanguageCode.en, name: 'Hi Fi Speakers' }],
  90. },
  91. ],
  92. });
  93. expect(createFacetValues.length).toBe(2);
  94. expect(pick(createFacetValues.find(fv => fv.code === 'pc')!, ['code', 'facet', 'name'])).toEqual({
  95. code: 'pc',
  96. facet: {
  97. id: 'T_2',
  98. name: 'Speaker Category',
  99. },
  100. name: 'PC Speakers',
  101. });
  102. expect(pick(createFacetValues.find(fv => fv.code === 'hi-fi')!, ['code', 'facet', 'name'])).toEqual({
  103. code: 'hi-fi',
  104. facet: {
  105. id: 'T_2',
  106. name: 'Speaker Category',
  107. },
  108. name: 'Hi Fi Speakers',
  109. });
  110. });
  111. it('updateFacetValues', async () => {
  112. const result = await adminClient.query<UpdateFacetValues.Mutation, UpdateFacetValues.Variables>(
  113. UPDATE_FACET_VALUES,
  114. {
  115. input: [
  116. {
  117. id: speakerTypeFacet.values[0].id,
  118. code: 'compact',
  119. },
  120. ],
  121. },
  122. );
  123. expect(result.updateFacetValues[0].code).toBe('compact');
  124. });
  125. it('facets', async () => {
  126. const result = await adminClient.query<GetFacetList.Query>(GET_FACET_LIST);
  127. const { items } = result.facets;
  128. expect(items.length).toBe(2);
  129. expect(items[0].name).toBe('category');
  130. expect(items[1].name).toBe('Speaker Category');
  131. brandFacet = items[0];
  132. speakerTypeFacet = items[1];
  133. });
  134. it('facet', async () => {
  135. const result = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  136. GET_FACET_WITH_VALUES,
  137. {
  138. id: speakerTypeFacet.id,
  139. },
  140. );
  141. expect(result.facet!.name).toBe('Speaker Category');
  142. });
  143. describe('deletion', () => {
  144. let products: GetProductListWithVariants.Items[];
  145. beforeAll(async () => {
  146. // add the FacetValues to products and variants
  147. const result1 = await adminClient.query<GetProductListWithVariants.Query>(
  148. GET_PRODUCTS_LIST_WITH_VARIANTS,
  149. );
  150. products = result1.products.items;
  151. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  152. input: {
  153. id: products[0].id,
  154. facetValueIds: [speakerTypeFacet.values[0].id],
  155. },
  156. });
  157. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  158. UPDATE_PRODUCT_VARIANTS,
  159. {
  160. input: [
  161. {
  162. id: products[0].variants[0].id,
  163. facetValueIds: [speakerTypeFacet.values[0].id],
  164. },
  165. ],
  166. },
  167. );
  168. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  169. input: {
  170. id: products[1].id,
  171. facetValueIds: [speakerTypeFacet.values[1].id],
  172. },
  173. });
  174. });
  175. it('deleteFacetValues deletes unused facetValue', async () => {
  176. const facetValueToDelete = speakerTypeFacet.values[2];
  177. const result1 = await adminClient.query<DeleteFacetValues.Mutation, DeleteFacetValues.Variables>(
  178. DELETE_FACET_VALUES,
  179. {
  180. ids: [facetValueToDelete.id],
  181. force: false,
  182. },
  183. );
  184. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  185. GET_FACET_WITH_VALUES,
  186. {
  187. id: speakerTypeFacet.id,
  188. },
  189. );
  190. expect(result1.deleteFacetValues).toEqual([
  191. {
  192. result: DeletionResult.DELETED,
  193. message: ``,
  194. },
  195. ]);
  196. expect(result2.facet!.values[0]).not.toEqual(facetValueToDelete);
  197. });
  198. it('deleteFacetValues for FacetValue in use returns NOT_DELETED', async () => {
  199. const facetValueToDelete = speakerTypeFacet.values[0];
  200. const result1 = await adminClient.query<DeleteFacetValues.Mutation, DeleteFacetValues.Variables>(
  201. DELETE_FACET_VALUES,
  202. {
  203. ids: [facetValueToDelete.id],
  204. force: false,
  205. },
  206. );
  207. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  208. GET_FACET_WITH_VALUES,
  209. {
  210. id: speakerTypeFacet.id,
  211. },
  212. );
  213. expect(result1.deleteFacetValues).toEqual([
  214. {
  215. result: DeletionResult.NOT_DELETED,
  216. message: `The selected FacetValue is assigned to 1 Product, 1 ProductVariant`,
  217. },
  218. ]);
  219. expect(result2.facet!.values.find(v => v.id === facetValueToDelete.id)).toBeDefined();
  220. });
  221. it('deleteFacetValues for FacetValue in use can be force deleted', async () => {
  222. const facetValueToDelete = speakerTypeFacet.values[0];
  223. const result1 = await adminClient.query<DeleteFacetValues.Mutation, DeleteFacetValues.Variables>(
  224. DELETE_FACET_VALUES,
  225. {
  226. ids: [facetValueToDelete.id],
  227. force: true,
  228. },
  229. );
  230. expect(result1.deleteFacetValues).toEqual([
  231. {
  232. result: DeletionResult.DELETED,
  233. message: `The selected FacetValue was removed from 1 Product, 1 ProductVariant and deleted`,
  234. },
  235. ]);
  236. // FacetValue no longer in the Facet.values array
  237. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  238. GET_FACET_WITH_VALUES,
  239. {
  240. id: speakerTypeFacet.id,
  241. },
  242. );
  243. expect(result2.facet!.values[0]).not.toEqual(facetValueToDelete);
  244. // FacetValue no longer in the Product.facetValues array
  245. const result3 = await adminClient.query<
  246. GetProductWithVariants.Query,
  247. GetProductWithVariants.Variables
  248. >(GET_PRODUCT_WITH_VARIANTS, {
  249. id: products[0].id,
  250. });
  251. expect(result3.product!.facetValues).toEqual([]);
  252. });
  253. it('deleteFacet that is in use returns NOT_DELETED', async () => {
  254. const result1 = await adminClient.query<DeleteFacet.Mutation, DeleteFacet.Variables>(
  255. DELETE_FACET,
  256. {
  257. id: speakerTypeFacet.id,
  258. force: false,
  259. },
  260. );
  261. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  262. GET_FACET_WITH_VALUES,
  263. {
  264. id: speakerTypeFacet.id,
  265. },
  266. );
  267. expect(result1.deleteFacet).toEqual({
  268. result: DeletionResult.NOT_DELETED,
  269. message: `The selected Facet includes FacetValues which are assigned to 1 Product`,
  270. });
  271. expect(result2.facet).not.toBe(null);
  272. });
  273. it('deleteFacet that is in use can be force deleted', async () => {
  274. const result1 = await adminClient.query<DeleteFacet.Mutation, DeleteFacet.Variables>(
  275. DELETE_FACET,
  276. {
  277. id: speakerTypeFacet.id,
  278. force: true,
  279. },
  280. );
  281. expect(result1.deleteFacet).toEqual({
  282. result: DeletionResult.DELETED,
  283. message: `The Facet was deleted and its FacetValues were removed from 1 Product`,
  284. });
  285. // FacetValue no longer in the Facet.values array
  286. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  287. GET_FACET_WITH_VALUES,
  288. {
  289. id: speakerTypeFacet.id,
  290. },
  291. );
  292. expect(result2.facet).toBe(null);
  293. // FacetValue no longer in the Product.facetValues array
  294. const result3 = await adminClient.query<
  295. GetProductWithVariants.Query,
  296. GetProductWithVariants.Variables
  297. >(GET_PRODUCT_WITH_VARIANTS, {
  298. id: products[1].id,
  299. });
  300. expect(result3.product!.facetValues).toEqual([]);
  301. });
  302. it('deleteFacet with no FacetValues works', async () => {
  303. const { createFacet } = await adminClient.query<CreateFacet.Mutation, CreateFacet.Variables>(
  304. CREATE_FACET,
  305. {
  306. input: {
  307. code: 'test',
  308. isPrivate: false,
  309. translations: [{ languageCode: LanguageCode.en, name: 'Test' }],
  310. },
  311. },
  312. );
  313. const result = await adminClient.query<DeleteFacet.Mutation, DeleteFacet.Variables>(
  314. DELETE_FACET,
  315. {
  316. id: createFacet.id,
  317. force: false,
  318. },
  319. );
  320. expect(result.deleteFacet.result).toBe(DeletionResult.DELETED);
  321. });
  322. });
  323. });
  324. export const GET_FACET_WITH_VALUES = gql`
  325. query GetFacetWithValues($id: ID!) {
  326. facet(id: $id) {
  327. ...FacetWithValues
  328. }
  329. }
  330. ${FACET_WITH_VALUES_FRAGMENT}
  331. `;
  332. const DELETE_FACET_VALUES = gql`
  333. mutation DeleteFacetValues($ids: [ID!]!, $force: Boolean) {
  334. deleteFacetValues(ids: $ids, force: $force) {
  335. result
  336. message
  337. }
  338. }
  339. `;
  340. const DELETE_FACET = gql`
  341. mutation DeleteFacet($id: ID!, $force: Boolean) {
  342. deleteFacet(id: $id, force: $force) {
  343. result
  344. message
  345. }
  346. }
  347. `;
  348. const GET_PRODUCTS_LIST_WITH_VARIANTS = gql`
  349. query GetProductListWithVariants {
  350. products {
  351. items {
  352. id
  353. name
  354. variants {
  355. id
  356. name
  357. }
  358. }
  359. totalItems
  360. }
  361. }
  362. `;
  363. export const CREATE_FACET_VALUES = gql`
  364. mutation CreateFacetValues($input: [CreateFacetValueInput!]!) {
  365. createFacetValues(input: $input) {
  366. ...FacetValue
  367. }
  368. }
  369. ${FACET_VALUE_FRAGMENT}
  370. `;
  371. export const UPDATE_FACET_VALUES = gql`
  372. mutation UpdateFacetValues($input: [UpdateFacetValueInput!]!) {
  373. updateFacetValues(input: $input) {
  374. ...FacetValue
  375. }
  376. }
  377. ${FACET_VALUE_FRAGMENT}
  378. `;