diff --git a/README.md b/README.md index b9312dac4..6ba2f7994 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,12 @@ scaleway-lib is a set of NPM packages used at Scaleway. ![npm bundle size](https://img.shields.io/bundlephobia/min/@scaleway/regex) ![npm](https://img.shields.io/npm/v/@scaleway/regex) +- [`@scaleway/jest-helpers`](./packages/jest-helpers/README.md): utilities jest functions. + + ![npm](https://img.shields.io/npm/dm/@scaleway/jest-helpers) + ![npm bundle size](https://img.shields.io/bundlephobia/min/@scaleway/jest-helpers) + ![npm](https://img.shields.io/npm/v/@scaleway/jest-helpers) + ## Development ### Locally diff --git a/packages/jest-helpers/.npmignore b/packages/jest-helpers/.npmignore new file mode 100644 index 000000000..d1811b877 --- /dev/null +++ b/packages/jest-helpers/.npmignore @@ -0,0 +1,3 @@ +**/__tests__/** +src +!.npmignore diff --git a/packages/jest-helpers/README.md b/packages/jest-helpers/README.md new file mode 100644 index 000000000..42700ce77 --- /dev/null +++ b/packages/jest-helpers/README.md @@ -0,0 +1,91 @@ +# `@scaleway/jest-helpers` + +## A package for utilities jest functions + +## Install + +```bash +$ yarn add @scaleway/jest-functions +``` + +## How to use + +### Create the helpers functions + +```tsx +import makeHelpers from '@scaleway/jest-helpers' + +const Wrapper = ({ children }) => ( + {children} +) + +export const { + renderWithTheme, + shouldMatchEmotionSnapshot, + shouldMatchEmotionSnapshotWithPortal, +} = makeHelpers(Wrapper) +``` + +#### With a theme prop + +```tsx +import makeHelpers from '@scaleway/jest-helpers' +import defaultTheme from '..' + +interface WrapperProps { + theme?: typeof defaultTheme +} + +const Wrapper = ({ theme, children }) => ( + {children} +) + +export const { + renderWithTheme, + shouldMatchEmotionSnapshot, + shouldMatchEmotionSnapshotWithPortal, +} = makeHelpers(Wrapper) +``` + +#### With CreateSerializerOptions + +```tsx +import makeHelpers from '@scaleway/jest-helpers' + +const Wrapper = ({ children }) => ( + {children} +) + +export const { + renderWithTheme, + shouldMatchEmotionSnapshot, + shouldMatchEmotionSnapshotWithPortal, +} = makeHelpers(Wrapper, { classNameReplacer: className => className }) +``` + +### renderWithTheme + +Automatically uses `CacheProvider` from `@emotion/cache`. Use it with a component, optional options & optional theme. + +```tsx +const renderWithTheme = ( + component: ReactNode, // The component to render + options?: RenderOptions, // RenderOptions from @testing-library/react + theme?: Theme, // Optional theme to use which will be passed to the Wrapper above +) => ReturnType +``` + +### shouldMatchEmotionSnapshot / shouldMatchEmotionSnapshotWithPortal + +Internally it uses the `renderWithTheme` generated from above. + +```tsx +const shouldMatchEmotionSnapshot = ( + component: ReactNode, // The component to render + options: { // In an object to make it backward-compatible and don't introduce any API breaking changes + options?: RenderOptions // RenderOptions from @testing-library/react + transform?: (node: ReturnType) => Promise | void // (a)sync function execute between the render and the expect. You can use this if you need mockAllIsIntersecting + theme?: Theme // Optional theme to use which will be passed to the Wrapper above + }, +) => Promise +``` diff --git a/packages/jest-helpers/package.json b/packages/jest-helpers/package.json new file mode 100644 index 000000000..ce6a5433f --- /dev/null +++ b/packages/jest-helpers/package.json @@ -0,0 +1,30 @@ +{ + "name": "@scaleway/jest-helpers", + "version": "1.0.0", + "description": "A package for utilities jest functions", + "type": "module", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/scaleway/scaleway-lib", + "directory": "packages/jest-helpers" + }, + "license": "MIT", + "dependencies": { + "@emotion/cache": "^11.1.3", + "@emotion/jest": "^11.3.0", + "@emotion/react": "^11.1.4", + "@testing-library/react": "^12.1.2" + }, + "devDependencies": { + "@types/react": "^17.0.27" + }, + "peerDependencies": { + "react": "^17.0.1" + } +} diff --git a/packages/jest-helpers/src/__tests__/__snapshots__/index.test.tsx.snap b/packages/jest-helpers/src/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..6706c2112 --- /dev/null +++ b/packages/jest-helpers/src/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`@jest-helpers should call tranform with shouldMatchEmotionSnapshot 1`] = ` + +
+
+
+ +`; + +exports[`@jest-helpers should call transform with shouldMatchEmotionSnapshot 1`] = ` + +
+
+
+ +`; + +exports[`@jest-helpers should render with renderWithTheme 1`] = ` +
+`; + +exports[`@jest-helpers should render with shouldMatchEmotionSnapshot 1`] = ` + +
+
+
+ +`; + +exports[`@jest-helpers should render with shouldMatchEmotionSnapshotWithPortal 1`] = ` + +
+
+
+ +`; diff --git a/packages/jest-helpers/src/__tests__/index.test.tsx b/packages/jest-helpers/src/__tests__/index.test.tsx new file mode 100644 index 000000000..beefd9578 --- /dev/null +++ b/packages/jest-helpers/src/__tests__/index.test.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import makeHelpers from ".." +import { RenderWithThemeFn } from "../helpers/renderWithTheme" +import { ShouldMatchEmotionSnapshotFn } from '../helpers/shouldMatchEmotionSnapshot' +import { ShouldMatchEmotionSnapshotWithPortalFn } from '../helpers/shouldMatchEmotionSnapshotWithPortal' + +describe('@jest-helpers', () => { + let renderWithTheme: RenderWithThemeFn + let shouldMatchEmotionSnapshot: ShouldMatchEmotionSnapshotFn + let shouldMatchEmotionSnapshotWithPortal: ShouldMatchEmotionSnapshotWithPortalFn + + beforeAll(() => { + const helpers = makeHelpers(({ children }) => ( +
+ {children} +
+ )) + + renderWithTheme = helpers.renderWithTheme + shouldMatchEmotionSnapshot = helpers.shouldMatchEmotionSnapshot + shouldMatchEmotionSnapshotWithPortal = helpers.shouldMatchEmotionSnapshotWithPortal + }) + + test('should render with renderWithTheme', () => { + const node = renderWithTheme(
) + const element = node.getByTestId("test") + + expect(element).toMatchSnapshot() + }) + + test('should render with shouldMatchEmotionSnapshot', async () => { + await shouldMatchEmotionSnapshot(
) + }) + + test('should call tranform with shouldMatchEmotionSnapshot', async () => { + const transform = jest.fn() + await shouldMatchEmotionSnapshot(
, { transform }) + + expect(transform).toHaveBeenCalledTimes(1) + }) + + test('should render with shouldMatchEmotionSnapshotWithPortal', async () => { + await shouldMatchEmotionSnapshotWithPortal(
) + }) + + it('should call transform with shouldMatchEmotionSnapshot', async () => { + const transform = jest.fn() + await shouldMatchEmotionSnapshotWithPortal(
, { transform }) + + expect(transform).toHaveBeenCalledTimes(1) + }) +}) \ No newline at end of file diff --git a/packages/jest-helpers/src/helpers/renderWithTheme.tsx b/packages/jest-helpers/src/helpers/renderWithTheme.tsx new file mode 100644 index 000000000..877b1e914 --- /dev/null +++ b/packages/jest-helpers/src/helpers/renderWithTheme.tsx @@ -0,0 +1,23 @@ +import createCache from '@emotion/cache' +import { CacheProvider } from '@emotion/react' +import { RenderOptions, render } from '@testing-library/react' +import React, { FC, ReactNode } from 'react' + +const emotionCache = createCache({ + key: 'cache', +}) + +emotionCache.compat = true + +export type RenderWithThemeFn = (component: ReactNode, options?: RenderOptions, theme?: Theme) => ReturnType + +export default function makeRenderWithTheme(Wrapper: FC<{ theme?: Theme }>): RenderWithThemeFn { + return (component, options, theme) => render( + + + {component} + + , + options, + ) +} diff --git a/packages/jest-helpers/src/helpers/shouldMatchEmotionSnapshot.ts b/packages/jest-helpers/src/helpers/shouldMatchEmotionSnapshot.ts new file mode 100644 index 000000000..ffbb096e0 --- /dev/null +++ b/packages/jest-helpers/src/helpers/shouldMatchEmotionSnapshot.ts @@ -0,0 +1,21 @@ +import { RenderOptions, render } from '@testing-library/react' +import { ReactNode } from 'react' +import { RenderWithThemeFn } from './renderWithTheme' + +interface Options { + options?: RenderOptions + transform?: (node: ReturnType) => Promise | void + theme?: Theme +} + +export type ShouldMatchEmotionSnapshotFn = (component: ReactNode, options?: Options) => Promise + +export default function makeShouldMatchEmotionSnapshot(renderWithTheme: RenderWithThemeFn): ShouldMatchEmotionSnapshotFn { + return async (component, { options, transform, theme } = {}) => { + const node = renderWithTheme(component, options, theme) + if (transform) await transform(node) + + expect(node.asFragment()).toMatchSnapshot() + node.unmount() + } +} diff --git a/packages/jest-helpers/src/helpers/shouldMatchEmotionSnapshotWithPortal.ts b/packages/jest-helpers/src/helpers/shouldMatchEmotionSnapshotWithPortal.ts new file mode 100644 index 000000000..81dd95769 --- /dev/null +++ b/packages/jest-helpers/src/helpers/shouldMatchEmotionSnapshotWithPortal.ts @@ -0,0 +1,34 @@ +import { RenderOptions, render } from '@testing-library/react' +import { ReactNode } from 'react' +import { RenderWithThemeFn } from './renderWithTheme' + +interface Options { + options?: RenderOptions + transform?: (node: ReturnType) => Promise | void + theme?: Theme +} + +export type ShouldMatchEmotionSnapshotWithPortalFn = (component: ReactNode, options?: Options) => Promise + +export default function makeShouldMatchEmotionSnapshotWithPortal(renderWithTheme: RenderWithThemeFn): ShouldMatchEmotionSnapshotWithPortalFn { + return async (component, { options, transform, theme } = {}) => { + // Save the instance of console (disable warning about adding element directly to document.body which is necessary when testing portal components) + const { console } = global + global.console = { ...console, error: jest.fn() } + + const node = renderWithTheme( + component, + { + container: document.body, + ...options, + }, + theme, + ) + if (transform) await transform(node) + expect(node.asFragment()).toMatchSnapshot() + + // Unmounting to don't see the warning message described above + node.unmount() + global.console = console + } +} diff --git a/packages/jest-helpers/src/index.ts b/packages/jest-helpers/src/index.ts new file mode 100644 index 000000000..d5f9c1cab --- /dev/null +++ b/packages/jest-helpers/src/index.ts @@ -0,0 +1,29 @@ +import { CreateSerializerOptions, createSerializer } from '@emotion/jest' +import { FC } from 'react' +import makeRenderWithTheme, { RenderWithThemeFn } from './helpers/renderWithTheme' +import makeShouldMatchEmotionSnapshot, { ShouldMatchEmotionSnapshotFn } from './helpers/shouldMatchEmotionSnapshot' +import makeShouldMatchEmotionSnapshotWithPortal, { ShouldMatchEmotionSnapshotWithPortalFn } from './helpers/shouldMatchEmotionSnapshotWithPortal' + +export { default as makeRenderWithTheme } from './helpers/renderWithTheme' + +type Helpers = { + renderWithTheme: RenderWithThemeFn + shouldMatchEmotionSnapshot: ShouldMatchEmotionSnapshotFn + shouldMatchEmotionSnapshotWithPortal: ShouldMatchEmotionSnapshotWithPortalFn +} + +export default function makeHelpers(Wrapper: FC<{ theme?: Theme }>, createSerializerOptions?: CreateSerializerOptions): Helpers { + expect.addSnapshotSerializer( + createSerializer(createSerializerOptions), + ) + + const renderWithTheme = makeRenderWithTheme(Wrapper) + const shouldMatchEmotionSnapshot = makeShouldMatchEmotionSnapshot(renderWithTheme) + const shouldMatchEmotionSnapshotWithPortal = makeShouldMatchEmotionSnapshotWithPortal(renderWithTheme) + + return { + renderWithTheme, + shouldMatchEmotionSnapshot, + shouldMatchEmotionSnapshotWithPortal, + } +} diff --git a/yarn.lock b/yarn.lock index 60a82bf31..441df54d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1101,6 +1101,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.13.10": + version "7.15.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" + integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.14.5", "@babel/template@^7.15.4", "@babel/template@^7.3.3": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" @@ -1278,6 +1285,90 @@ dependencies: chalk "^4.0.0" +"@emotion/cache@^11.1.3", "@emotion/cache@^11.4.0": + version "11.4.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.4.0.tgz#293fc9d9a7a38b9aad8e9337e5014366c3b09ac0" + integrity sha512-Zx70bjE7LErRO9OaZrhf22Qye1y4F7iDl+ITjet0J+i+B88PrAOBkKvaAWhxsZf72tDLajwCgfCjJ2dvH77C3g== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.0.0" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "^4.0.3" + +"@emotion/css-prettifier@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/css-prettifier/-/css-prettifier-1.0.0.tgz#3ed4240d93c9798c001cedf27dd0aa960bdddd1a" + integrity sha512-efxSrRTiTqHTQVKW15Gz5H4pNAw8OqcG8NaiwkJIkqIdNXTD4Qr1zC1Ou6r2acd1oJJ2s56nb1ClnXMiWoj6gQ== + dependencies: + "@emotion/memoize" "^0.7.4" + stylis "^4.0.3" + +"@emotion/hash@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" + integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== + +"@emotion/jest@^11.3.0": + version "11.3.0" + resolved "https://registry.yarnpkg.com/@emotion/jest/-/jest-11.3.0.tgz#43bed6dcb47c8691b346cee231861ebc8f9b0016" + integrity sha512-LZqYc3yerhic1IvAcEwBLRs1DsUt3oY7Oz6n+e+HU32iYOK/vpfzlhgmQURE94BHfv6eCOj6DV38f3jSnIkBkQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/css-prettifier" "^1.0.0" + chalk "^4.1.0" + specificity "^0.4.1" + stylis "^4.0.3" + +"@emotion/memoize@^0.7.4": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" + integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== + +"@emotion/react@^11.1.4": + version "11.4.1" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.4.1.tgz#a1b0b767b5bad57515ffb0cad9349614d27f4d57" + integrity sha512-pRegcsuGYj4FCdZN6j5vqCALkNytdrKw3TZMekTzNXixRg4wkLsU5QEaBG5LC6l01Vppxlp7FE3aTHpIG5phLg== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/cache" "^11.4.0" + "@emotion/serialize" "^1.0.2" + "@emotion/sheet" "^1.0.2" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965" + integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A== + dependencies: + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.4" + "@emotion/unitless" "^0.7.5" + "@emotion/utils" "^1.0.0" + csstype "^3.0.2" + +"@emotion/sheet@^1.0.0", "@emotion/sheet@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.2.tgz#1d9ffde531714ba28e62dac6a996a8b1089719d0" + integrity sha512-QQPB1B70JEVUHuNtzjHftMGv6eC3Y9wqavyarj4x4lg47RACkeSfNo5pxIOKizwS9AEFLohsqoaxGQj4p0vSIw== + +"@emotion/unitless@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + +"@emotion/utils@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af" + integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA== + +"@emotion/weak-memoize@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" + integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== + "@endemolshinegroup/cosmiconfig-typescript-loader@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz#eea4635828dde372838b0909693ebd9aafeec22d" @@ -2519,7 +2610,7 @@ "@types/react-test-renderer" ">=16.9.0" react-error-boundary "^3.1.0" -"@testing-library/react@^12.0.0": +"@testing-library/react@^12.0.0", "@testing-library/react@^12.1.2": version "12.1.2" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.2.tgz#f1bc9a45943461fa2a598bb4597df1ae044cfc76" integrity sha512-ihQiEOklNyHIpo2Y8FREkyD1QAea054U0MVbwH1m8N9TxeFz+KoJ9LkqoKqJlzx2JDm56DVwaJ1r36JYxZM05g== @@ -2699,6 +2790,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^17.0.27": + version "17.0.27" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.27.tgz#6498ed9b3ad117e818deb5525fa1946c09f2e0e6" + integrity sha512-zgiJwtsggVGtr53MndV7jfiUESTqrbxOcBvwfe6KS/9bzaVPCTDieTWnFNecVNx6EAaapg5xsLLWFfHHR437AA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -4930,7 +5030,7 @@ history@^4.9.0: tiny-warning "^1.0.0" value-equal "^1.0.1" -hoist-non-react-statics@^3.1.0: +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -8113,6 +8213,11 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== +specificity@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" + integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== + split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" @@ -8320,6 +8425,11 @@ strong-log-transformer@^2.1.0: minimist "^1.2.0" through "^2.3.4" +stylis@^4.0.3: + version "4.0.10" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240" + integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg== + supports-color@8.1.1, supports-color@^8.0.0: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"