Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug/issue 271 determinism #292

Merged
merged 12 commits into from
Mar 2, 2020
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