Skip to content

Commit

Permalink
Bug/issue 271 determinism (#292)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
thescientist13 committed Mar 15, 2020
1 parent babca10 commit 8d1529a
Show file tree
Hide file tree
Showing 10 changed files with 482 additions and 47 deletions.
2 changes: 1 addition & 1 deletion packages/cli/src/data/schema/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
});
Expand Down
23 changes: 17 additions & 6 deletions packages/cli/src/lifecycles/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/lifecycles/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});
};

Expand Down
42 changes: 14 additions & 28 deletions packages/cli/test/cases/build.data.graph/build.data.graph.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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'));
});
Expand All @@ -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__ <script> tag set in index.html', () => {
it('should have one window.__APOLLO_STATE__ <script> with (approximated) expected state', () => {
const scriptTags = dom.window.document.querySelectorAll('head > script');
const apolloScriptTags = Array.prototype.slice.call(scriptTags).filter(script => {
return script.getAttribute('data-state') === 'apollo';
});
const innerHTML = apolloScriptTags[0].innerHTML;

expect(apolloScriptTags.length).to.be.equal(1);
expect(apolloScriptTags[0].innerHTML).to.contain(`window.__APOLLO_STATE__=${JSON.stringify(expectedCache)}`);
expect(apolloScriptTags.length).to.equal(1);
expect(innerHTML).to.match(apolloStateRegex);
});

it('should have a <header> tag in the <body>', function() {
Expand All @@ -109,8 +102,6 @@ describe('Build Greenwood With: ', function() {
});

describe('Blog Page (Template) w/ Navigation and Children Query', function() {
const expectedCache = {"ROOT_QUERY.children({\"parent\":\"blog\"}).0":{"title":"Blog","link":"/blog/first-post","__typename":"Page"},"ROOT_QUERY.children({\"parent\":\"blog\"}).1":{"title":"Blog","link":"/blog/second-post","__typename":"Page"},"ROOT_QUERY":{"children({\"parent\":\"blog\"})":[{"type":"id","generated":true,"id":"ROOT_QUERY.children({\"parent\":\"blog\"}).0","typename":"Page"},{"type":"id","generated":true,"id":"ROOT_QUERY.children({\"parent\":\"blog\"}).1","typename":"Page"}],"navigation":[{"type":"id","generated":true,"id":"ROOT_QUERY.navigation.0","typename":"Navigation"}]},"ROOT_QUERY.navigation.0":{"label":"Blog","link":"/blog/","__typename":"Navigation"}}; // eslint-disable-line

beforeEach(async function() {
dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, 'blog', 'first-post', 'index.html'));
});
Expand All @@ -123,22 +114,20 @@ describe('Build Greenwood With: ', function() {
expect(await glob.promise(path.join(this.context.publicDir, 'blog', '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', function() {
it('should output one cache.json file to be defined', function() {
const cacheContents = require(path.join(this.context.publicDir, 'blog', 'cache.json'));

expect(cacheContents).to.be.deep.equalInAnyOrder(expectedCache);
expect(cacheContents).to.not.be.undefined;
});

it('should have one window.__APOLLO_STATE__ <script> tag set in index.html', () => {
it('should have one window.__APOLLO_STATE__ <script> with (approximated) expected state', () => {
const scriptTags = dom.window.document.querySelectorAll('head > script');
const apolloScriptTags = Array.prototype.slice.call(scriptTags).filter(script => {
return script.getAttribute('data-state') === 'apollo';
});

expect(apolloScriptTags.length).to.be.equal(1);
// TODO fixing the ordering issue would help make this test case more reliable - #271
// expect(apolloScriptTags[0].innerHTML).to.contain(`window.__APOLLO_STATE__=${JSON.stringify(expectedCache)}`);
expect(apolloScriptTags[0].innerHTML).to.match(apolloStateRegex);
});

it('should have a <header> tag in the <body>', function() {
Expand All @@ -164,18 +153,15 @@ describe('Build Greenwood With: ', function() {
expect(listItems.length).to.be.equal(2);
expect(linkItems.length).to.be.equal(2);

// TODO fixing the ordering issue would remove need to rely on ordering for testing - #371
// e.g. these should be .equal, not .contain
const link1 = linkItems[0];
const link2 = linkItems[1];

expect(link1.href.replace('file://', '')).to.be.contain('/blog/');
expect(link1.title).to.be.contain('Click to read my');
expect(link1.href.replace('file://', '')).to.be.equal('/blog/first-post/');
expect(link1.title).to.be.equal('Click to read my Blog blog post');
expect(link1.innerHTML).to.contain('Blog');

const link2 = linkItems[1];

expect(link2.href.replace('file://', '')).to.be.contain('/blog/');
expect(link2.title).to.be.contain('Click to read my');
expect(link2.href.replace('file://', '')).to.be.equal('/blog/second-post/');
expect(link2.title).to.be.equal('Click to read my Blog blog post');
expect(link2.innerHTML).to.contain('Blog');
});
});
Expand Down
Loading

0 comments on commit 8d1529a

Please sign in to comment.