From 8d1529a217b47e7ee74881e901700b5160e77a58 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sun, 1 Mar 2020 20:47:22 -0500 Subject: [PATCH] Bug/issue 271 determinism (#292) * Rfc/issue 115 build time data access (#269) * graphql server working * apollo client connect to apollo server * connected header example using lit apollo * todo * todos * query and client + server refactor * schema refactoring * clean up console logging * alias all @greenwood/cli/data module imports * avoid paramater destructuring * graphql example in the header * multiple schemas * internal data sources documentation * shelf refactor and children query integration * refactor out ApolloQuery * ability to intercept client.query calls * basic semi-working implementation * remove extra config from server context * have puppeteer wait for graphql requests before returning content * fix and add test cases for apollo * merged resolvers not actually working * multiple queries support * everything working * todos * TODO tracking * fix fallback apollo client fetch handling * full test suite * cache json test cases * stablize test due to inconsistent data results ordering * clean up deps * todo cleanup * remove forced client call in SSG mode for client * represent graph through the schema * updated data docs * typos and grammer * typos and community link fixes * unit test data graph * linting * lint fix * added ordered index tracking when serializing pages * formatting * document graph page ordering * cli data graph unit tests and expand mocha spec globbing * address missing hash in mock graph * loosen graph test case asserts * update graph tests to focus less on implementation details * add back missing develop task --- packages/cli/src/data/schema/graph.js | 2 +- packages/cli/src/lifecycles/graph.js | 23 +- packages/cli/src/lifecycles/serialize.js | 6 +- .../build.data.graph/build.data.graph.spec.js | 42 +-- packages/cli/test/unit/data/mocks/graph.js | 279 ++++++++++++++++++ .../cli/test/unit/data/schema/graph.spec.js | 152 ++++++++++ test/smoke-test.js | 9 + www/components/shelf/shelf.js | 6 +- www/pages/docs/data.md | 6 +- www/pages/getting-started/index.md | 4 - 10 files changed, 482 insertions(+), 47 deletions(-) create mode 100644 packages/cli/test/unit/data/mocks/graph.js create mode 100644 packages/cli/test/unit/data/schema/graph.spec.js diff --git a/packages/cli/src/data/schema/graph.js b/packages/cli/src/data/schema/graph.js index 8901fca27..7840af4c5 100644 --- a/packages/cli/src/data/schema/graph.js +++ b/packages/cli/src/data/schema/graph.js @@ -56,7 +56,7 @@ const getNavigationFromGraph = async (root, query, context) => { } }); - // TODO best format for users, hash map? #271 + // TODO best format for users, hash map? #288 return Object.keys(navigation).map((key) => { return navigation[key]; }); diff --git a/packages/cli/src/lifecycles/graph.js b/packages/cli/src/lifecycles/graph.js index 8022c7183..b457e5629 100644 --- a/packages/cli/src/lifecycles/graph.js +++ b/packages/cli/src/lifecycles/graph.js @@ -9,17 +9,26 @@ const createGraphFromPages = async (pagesDir, config) => { return new Promise(async (resolve, reject) => { try { + const pagesIndexMap = new Map(); + let pagesIndex = 0; const walkDirectory = async(directory) => { let files = await fs.readdir(directory); - return Promise.all(files.map(async (file) => { + return Promise.all(files.map((file) => { + const filenameHash = crypto.createHash('md5').update(`${directory}/${file}`).digest('hex'); + const filePath = path.join(directory, file); + const stats = fs.statSync(filePath); + const isMdFile = file.substr(file.length - 2, file.length) === 'md'; + + // map each page to a (0 based) index based on filesystem order + if (isMdFile) { + pagesIndexMap.set(filenameHash, pagesIndex); + pagesIndex += 1; + } + return new Promise(async (resolve, reject) => { try { - const filePath = path.join(directory, file); - const stats = await fs.stat(filePath); - const isMdFile = file.substr(file.length - 2, file.length) === 'md'; - if (isMdFile && !stats.isDirectory()) { const fileContents = await fs.readFile(filePath, 'utf8'); const { attributes } = fm(fileContents); @@ -80,12 +89,14 @@ const createGraphFromPages = async (pagesDir, config) => { * meta: og graph meta array of objects { property/name, content } */ - pages.push({ mdFile, label, route, template, filePath, fileName, relativeExpectedPath, title, meta }); + pages[pagesIndexMap.get(filenameHash)] = { mdFile, label, route, template, filePath, fileName, relativeExpectedPath, title, meta }; } + if (stats.isDirectory()) { await walkDirectory(filePath); resolve(); } + resolve(); } catch (err) { reject(err); diff --git a/packages/cli/src/lifecycles/serialize.js b/packages/cli/src/lifecycles/serialize.js index 30b5896c5..6e75b05df 100644 --- a/packages/cli/src/lifecycles/serialize.js +++ b/packages/cli/src/lifecycles/serialize.js @@ -24,9 +24,11 @@ const setDataForPages = async (context) => { cacheContents = deepmerge(cacheContents, require(file)); }); + const serialzedCacheContents = JSON.stringify(cacheContents); + // TODO could optimize this probably - #277 - fs.writeFileSync(`${publicDir}/${cacheRoot}/cache.json`, JSON.stringify(cacheContents)); - fs.writeFileSync(pagePath, contents.replace('___DATA___', JSON.stringify(cacheContents))); + fs.writeFileSync(`${publicDir}/${cacheRoot}/cache.json`, serialzedCacheContents); + fs.writeFileSync(pagePath, contents.replace('___DATA___', serialzedCacheContents)); }); }; diff --git a/packages/cli/test/cases/build.data.graph/build.data.graph.spec.js b/packages/cli/test/cases/build.data.graph/build.data.graph.spec.js index c7ee32085..4e4ec88d2 100644 --- a/packages/cli/test/cases/build.data.graph/build.data.graph.spec.js +++ b/packages/cli/test/cases/build.data.graph/build.data.graph.spec.js @@ -32,6 +32,7 @@ const TestBed = require('../../../../../test/test-bed'); describe('Build Greenwood With: ', function() { const LABEL = 'Data from GraphQL'; + const apolloStateRegex = /window.__APOLLO_STATE__=({.*?});/; let setup; before(async function() { @@ -48,8 +49,6 @@ describe('Build Greenwood With: ', function() { runSmokeTest(['public', 'not-found'], LABEL); describe('Home (Page Template) w/ Navigation Query', function() { - const expectedCache = {"ROOT_QUERY.navigation.0":{"label":"Blog","link":"/blog/","__typename":"Navigation"},"ROOT_QUERY":{"navigation":[{"type":"id","generated":true,"id":"ROOT_QUERY.navigation.0","typename":"Navigation"}]}}; // eslint-disable-line - beforeEach(async function() { dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, 'index.html')); }); @@ -74,21 +73,15 @@ describe('Build Greenwood With: ', function() { expect(await glob.promise(path.join(this.context.publicDir, './cache.json'))).to.have.lengthOf(1); }); - // TODO fixing the ordering issue would help make this test case more reliable - #271 - xit('should output one cache.json file with expected cache contents', async function() { - const cacheContents = require(path.join(this.context.publicDir, './cache.json')); - - expect(cacheContents).to.be.deep.equalInAnyOrder(expectedCache); - }); - - it('should have one window.__APOLLO_STATE__