Skip to content
This repository has been archived by the owner on Sep 22, 2023. It is now read-only.

Commit

Permalink
Merge pull request #279 from 30-seconds/collections-stage-3
Browse files Browse the repository at this point in the history
Collections stage 3
  • Loading branch information
Chalarangelo authored Jan 25, 2021
2 parents e077c9d + 256e7df commit 56dd025
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 12 deletions.
2 changes: 1 addition & 1 deletion content/configs
65 changes: 65 additions & 0 deletions src/blocks/entities/collectionConfig/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { InstanceCache } from 'blocks/utilities/instanceCache';
import { ArgsError } from 'blocks/utilities/error';
import { convertToSeoSlug } from 'utils';

/**
* A collection configuration (i.e. the data and metadata from a content/configs JSON file).
*/
export class CollectionConfig {
/**
* Create a collection configuration from the JSON data given.
* @param {object} config - Collection configuration data. Must contain:
* - `name` - The name of the configuration.
* - `snippetIds` - Ids of the snippets that make up the collection.
* - `featured` - > 0 if the content is listed, -1 if it's not.
* - `slug` - Base url for the content pages.
* @throws Will throw an error if any of the necessary keys is not present.
*/
constructor({
name,
slug,
snippetIds,
featured,
description,
theme = null,
...rest
}) {
if (!name || !slug || !featured || !snippetIds || !snippetIds.length) {
throw new ArgsError(
"Missing required keys. One or more of the following keys were not specified: 'name', 'slug', 'featured', 'snippetIds'"
);
}

this.name = name;
this.description = description;
this.slug = slug;
this.featured = featured;
this.theme = theme;
this.snippetIds = snippetIds;
Object.keys(rest).forEach(key => {
this[key] = rest[key];
});

CollectionConfig.instances.add(this.id, this);

return this;
}

static instances = new InstanceCache();

get id() {
return `${this.slug}`;
}

get icon() {
return this.theme ? this.theme.iconName : null;
}

get assetPath() {
return `/${global.settings.paths.staticAssetPath}`;
}

get outPath() {
return global.settings.paths.contentPath;
}
}
74 changes: 74 additions & 0 deletions src/blocks/entities/collectionConfig/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { CollectionConfig } from '.';
import { ArgsError } from 'blocks/utilities/error';
import { rawCollections } from 'fixtures/blocks/collectionConfigs';
import { Env } from 'blocks/utilities/env';

describe('ColectionConfig', () => {
beforeAll(() => {
Env.setup();
});

describe('constructor', () => {
it('throws an error if called without all required keys', () => {
expect(() => new CollectionConfig({})).toThrow(ArgsError);
});
});

describe('constructed with valid data', () => {
let collectionConfigs = {};
beforeAll(() => {
collectionConfigs.collection = new CollectionConfig(
rawCollections.collection
);
});

it('should contain all passed data', () => {
expect(collectionConfigs.collection.name).toBe(
rawCollections.collection.name
);
expect(collectionConfigs.collection.description).toBe(
rawCollections.collection.description
);
expect(collectionConfigs.collection.slug).toBe(
rawCollections.collection.slug
);
expect(collectionConfigs.collection.featured).toBe(
rawCollections.collection.featured
);
expect(collectionConfigs.collection.snippetIds).toEqual(
rawCollections.collection.snippetIds
);
expect(collectionConfigs.collection.theme).toEqual(
rawCollections.collection.theme
);
});

it('should produce the correct id', () => {
expect(collectionConfigs.collection.id).toBe(
rawCollections.collection.slug
);
});

it('should produce the correct icon', () => {
expect(collectionConfigs.collection.icon).toBe(
rawCollections.collection.theme.iconName
);
});

it('should return the correct asset path', () => {
expect(
collectionConfigs.collection.assetPath.endsWith(
global.settings.paths.staticAssetPath
)
).toBe(true);
});

it('should return the correct output path', () => {
expect(
collectionConfigs.collection.outPath.endsWith(
global.settings.paths.contentPath
)
).toBe(true);
});
});
});
7 changes: 6 additions & 1 deletion src/blocks/entities/snippet/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ export class Snippet {
static instances = new InstanceCache();

get id() {
return `${this.config.sourceDir}/${this.fileName.slice(0, -3)}`;
if (!this._id) {
this._id = `${this.config.slugPrefix}${convertToSeoSlug(
this.fileName.slice(0, -3)
)}`;
}
return this._id;
}

get slug() {
Expand Down
2 changes: 1 addition & 1 deletion src/blocks/entities/snippet/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('Snippet', () => {

it('produces a valid id', () => {
expect(snippet.id).toBe(
`${snippet.config.sourceDir}/${snippet.fileName.slice(0, -3)}`
`${snippet.config.slugPrefix}/${snippet.fileName.slice(0, -3)}`
);
});

Expand Down
31 changes: 25 additions & 6 deletions src/blocks/entities/snippetCollection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export class SnippetCollection {
* @param {object} collectionInfo - Collection data. Required keys:
* - `type`: One of ['main', 'blog', 'language', 'tag', 'collection']
* - `slugPrefix`: The prefix for listed slugs in the collection.
* - `config`: The ContentConfig of the collection. Required by 'language' and 'tag' types.
* - `config`: One of the following:
* - The ContentConfig of the collection. Required by 'language' and 'tag' types.
* - The CollectionConfig of the collection. Required by the collection' type.
* - `parentCollection`: The parent SnippetCollection. Required by the 'tag' type.
* - `tag`: The tag name (string) of the collection. Required by the 'tag' type.
* @param {Array<Snippet>} snippets - An array of Snippet objects.
Expand Down Expand Up @@ -43,7 +45,7 @@ export class SnippetCollection {
);
}

if (['language', 'tag'].includes(type) && !rest.config) {
if (['language', 'tag', 'collection'].includes(type) && !rest.config) {
throw new ArgsError(
`Missing required argument. 'config' must be a non-empty object when the type is '${type}'.`
);
Expand All @@ -57,7 +59,10 @@ export class SnippetCollection {

this.type = type;
this.slugPrefix = slugPrefix;
this.snippets = snippets.sort((a, b) => b.ranking - a.ranking);
this.snippets =
type === 'collection'
? snippets
: snippets.sort((a, b) => b.ranking - a.ranking);
Object.keys(rest).forEach(key => {
if (key === 'name') this._name = rest[key];
else this[key] = rest[key];
Expand Down Expand Up @@ -86,7 +91,8 @@ export class SnippetCollection {
*/
addSnippets = snippets => {
this.snippets.push(...snippets);
this.snippets.sort((a, b) => b.ranking - a.ranking);
if (this.type !== 'collection')
this.snippets.sort((a, b) => b.ranking - a.ranking);
return this;
};

Expand Down Expand Up @@ -137,6 +143,9 @@ export class SnippetCollection {
case 'blog':
this._name = literals.listing.blog;
break;
case 'collection':
this._name = this.config.name;
break;
case 'language':
this._name = literals.listing.codelang(this.config.language.long);
break;
Expand Down Expand Up @@ -168,6 +177,9 @@ export class SnippetCollection {
case 'blog':
this._shortName = literals.listing.blog;
break;
case 'collection':
this._shortName = this.config.name;
break;
case 'language':
this._shortName = literals.listing.shortCodelang(
this.config.language.long
Expand Down Expand Up @@ -200,6 +212,12 @@ export class SnippetCollection {
case 'blog':
this._description = null;
break;
case 'collection':
this._description =
this.config.description && this.config.description.length
? this.config.description
: null;
break;
case 'language':
this._description =
this.config.description && this.config.description.length
Expand Down Expand Up @@ -260,7 +278,8 @@ export class SnippetCollection {
}

get icon() {
if (!['language', 'blog', 'tag'].includes(this.type)) return undefined;
if (!['language', 'blog', 'tag', 'collection'].includes(this.type))
return undefined;
return this.config.theme && this.config.theme.iconName;
}

Expand All @@ -285,7 +304,7 @@ export class SnippetCollection {

get isListed() {
if (!this._isListed) {
if (['blog', 'main'].includes(this.type)) {
if (['blog', 'main', 'collection'].includes(this.type)) {
this._isListed = true;
} else if (['language', 'tag'].includes(this.type)) {
this._isListed = this.config.featured > 0;
Expand Down
39 changes: 37 additions & 2 deletions src/blocks/entities/snippetCollection/index.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { SnippetCollection } from '.';
import { Snippet } from 'blocks/entities/snippet';
import { ContentConfig } from 'blocks/entities/contentConfig';
import { CollectionConfig } from 'blocks/entities/collectionConfig';
import { ArgsError } from 'blocks/utilities/error';
import { rawConfigs } from 'fixtures/blocks/contentConfigs';
import { rawCollections } from 'fixtures/blocks/collectionConfigs';
import { rawSnippets } from 'fixtures/blocks/snippets';
import { Env } from 'blocks/utilities/env';

describe('SnippetCollection', () => {
let configs = {};
let collectionConfigs = {};
let snippets = [];
beforeAll(() => {
Env.setup();
Object.keys(rawConfigs).forEach(name => {
configs[name] = new ContentConfig(rawConfigs[name]);
});
Object.keys(rawCollections).forEach(name => {
collectionConfigs[name] = new CollectionConfig(rawCollections[name]);
});
snippets.push(new Snippet(rawSnippets.normal, configs.react));
snippets.push(new Snippet(rawSnippets.blog, configs.blog));
snippets.push(new Snippet(rawSnippets.normal, configs.dart));
Expand Down Expand Up @@ -120,6 +126,14 @@ describe('SnippetCollection', () => {
},
[snippets[2]]
);
collections.collection = new SnippetCollection(
{
type: 'collection',
config: collectionConfigs.collection,
slugPrefix: `/${collectionConfigs.collection.slug}`,
},
collectionConfigs.collection.snippetIds.map(id => Snippet.instances[id])
);
});

it('should store collection metadata', () => {
Expand All @@ -132,12 +146,18 @@ describe('SnippetCollection', () => {
expect(collections.tag.parentCollection).toBe(collections.language);
});

it('sorts snippets in the collection', () => {
it('sorts snippets in non-collection-type collections', () => {
expect(collections.main.snippets.map(s => s.name)).toEqual(
snippets.sort((a, b) => b.ranking - a.ranking).map(s => s.name)
);
});

it('keeps snippets unsorted in collection-type collections', () => {
expect(collections.collection.snippets.map(s => s.id)).toEqual(
rawCollections.collection.snippetIds
);
});

it('should produce the correct id', () => {
expect(collections.language.id).toBe('language/dart');
});
Expand Down Expand Up @@ -172,6 +192,7 @@ describe('SnippetCollection', () => {
expect(collections.blog.name).not.toBe(null);
expect(collections.language.name).not.toBe(null);
expect(collections.tag.name).not.toBe(null);
expect(collections.collection.name).not.toBe(null);
expect(collections.tagWithMetadata.name).not.toBe(collections.tag.name);
});

Expand All @@ -180,6 +201,7 @@ describe('SnippetCollection', () => {
expect(collections.blog.description).toBe(null);
expect(collections.language.description).not.toBe(null);
expect(collections.tag.description).not.toBe(null);
expect(collections.collection.description).not.toBe(null);
expect(collections.tagWithMetadata.description).not.toBe(null);
});

Expand All @@ -206,6 +228,9 @@ describe('SnippetCollection', () => {
it('should produce the correct icon', () => {
expect(collections.language.icon).toBe(configs.dart.theme.iconName);
expect(collections.tag.icon).toBe(configs.dart.theme.iconName);
expect(collections.collection.icon).toBe(
collectionConfigs.collection.theme.iconName
);
});

it('should produce the correct url', () => {
Expand Down Expand Up @@ -236,24 +261,34 @@ describe('SnippetCollection', () => {
expect(collections.blog.isListed).toBe(true);
expect(collections.language.isListed).toBe(false);
expect(collections.tag.isListed).toBe(false);
expect(collections.collection.isListed).toBe(true);
});

describe('addSnippets', () => {
beforeAll(() => {
collections.language.addSnippets([snippets[1]]);
collections.collection.addSnippets([snippets[1]]);
});

it('should add snippets to the collection', () => {
expect(collections.language.snippets.length).toBe(2);
expect(collections.collection.snippets.length).toBe(3);
});

it('sorts snippets in the collection', () => {
it('sorts snippets in non-collection-type collections', () => {
expect(collections.language.snippets.map(s => s.name)).toEqual(
collections.language.snippets
.sort((a, b) => b.ranking - a.ranking)
.map(s => s.name)
);
});

it('keeps snippets unsorted in non-collection-type collections', () => {
expect(collections.collection.snippets.map(s => s.id)).toEqual([
...rawCollections.collection.snippetIds,
snippets[1].id,
]);
});
});
});
});
Loading

0 comments on commit 56dd025

Please sign in to comment.