Initiatives: add normalizeNodeEntry implementation (#1754)

This is where most flexibility when hand-writing JSON files is expected
to come from. As it makes few assumptions about the formatting but the
internal normalized type is still consistent.
This commit is contained in:
Robin van Boven 2020-04-23 16:12:26 +02:00 committed by GitHub
parent d426eb7f06
commit e6a988b1a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 156 additions and 0 deletions

View File

@ -3,6 +3,7 @@
import {type URL} from "../../core/references";
import {type NodeWeight} from "../../core/weights";
import {type TimestampMs, type TimestampISO} from "../../util/timestamp";
import * as Timestamp from "../../util/timestamp";
/**
* Represents an "inline contribution" node. They're called entries and named
@ -38,8 +39,53 @@ export type NodeEntryJson = $Shape<{
// Key defaults to a url-friendly-slug of the title. Override it if you need
// to preserve a specific NodeAddress, or the slug produces duplicate keys.
+key: string,
// Defaults to an empty array.
+contributors: $ReadOnlyArray<URL>,
// Timestamp of this node, but in ISO format as it's more human friendly.
+timestampIso: TimestampISO,
// Defaults to null.
+weight: NodeWeight | null,
}>;
/**
* Takes a NodeEntryJson and normalizes it to a NodeEntry.
*
* Will throw when required fields are missing. Otherwise handles default
* values and converting ISO timestamps.
*/
export function normalizeNodeEntry(
input: NodeEntryJson,
defaultTimestampMs: TimestampMs
): NodeEntry {
if (!input.title) {
throw new TypeError(
`Title is required for an entry, received ${JSON.stringify(input)}`
);
}
return {
key: input.key || _titleSlug(input.title),
title: input.title,
timestampMs: input.timestampIso
? Timestamp.fromISO(input.timestampIso)
: defaultTimestampMs,
contributors: input.contributors || [],
weight: input.weight || null,
};
}
/**
* Creates a url-friendly-slug from the title of a NodeEntry. Useful for
* generating a default key.
*
* Note: keys are not required to meet the formatting rules of this slug,
* this is mostly for predictability and convenience of NodeAddresses.
*/
export function _titleSlug(title: string): string {
return String(title)
.toLowerCase()
.replace(/[^a-z0-9-_]+/g, "-")
.replace(/--+/g, "-")
.replace(/^-/, "")
.replace(/-$/, "");
}

View File

@ -0,0 +1,110 @@
// @flow
import {type TimestampMs} from "../../util/timestamp";
import * as Timestamp from "../../util/timestamp";
import {
type NodeEntry,
type NodeEntryJson,
normalizeNodeEntry,
_titleSlug,
} from "./nodeEntry";
describe("plugins/initiatives/nodeEntry", () => {
describe("normalizeNodeEntry", () => {
it("should throw without a title", () => {
const timestampMs: TimestampMs = Timestamp.fromNumber(123);
const entry: NodeEntryJson = {key: "no-title"};
const f = () => normalizeNodeEntry(entry, timestampMs);
expect(f).toThrow(TypeError);
});
it("should handle a minimal entry", () => {
const timestampMs: TimestampMs = Timestamp.fromNumber(123);
const entry: NodeEntryJson = {title: "Most minimal"};
const expected: NodeEntry = {
title: "Most minimal",
key: "most-minimal",
contributors: [],
timestampMs,
weight: null,
};
expect(normalizeNodeEntry(entry, timestampMs)).toEqual(expected);
});
it("should handle an entry with weights", () => {
const timestampMs: TimestampMs = Timestamp.fromNumber(123);
const entry: NodeEntryJson = {title: "Include weight", weight: 42};
const expected: NodeEntry = {
title: "Include weight",
key: "include-weight",
contributors: [],
timestampMs,
weight: 42,
};
expect(normalizeNodeEntry(entry, timestampMs)).toEqual(expected);
});
it("should handle an entry with contributors", () => {
const timestampMs: TimestampMs = Timestamp.fromNumber(123);
const entry: NodeEntryJson = {
title: "Include contributors",
contributors: ["https://foo.bar/u/abc"],
};
const expected: NodeEntry = {
title: "Include contributors",
key: "include-contributors",
contributors: ["https://foo.bar/u/abc"],
timestampMs,
weight: null,
};
expect(normalizeNodeEntry(entry, timestampMs)).toEqual(expected);
});
it("should handle an entry with timestamp", () => {
const timestampMs: TimestampMs = Timestamp.fromNumber(123);
const entry: NodeEntryJson = {
title: "Include timestamp",
timestampIso: Timestamp.toISO(Date.parse("2018-02-03T12:34:56.789Z")),
};
const expected: NodeEntry = {
title: "Include timestamp",
key: "include-timestamp",
contributors: [],
timestampMs: Timestamp.fromNumber(
Date.parse("2018-02-03T12:34:56.789Z")
),
weight: null,
};
expect(normalizeNodeEntry(entry, timestampMs)).toEqual(expected);
});
it("should handle an entry with key", () => {
const timestampMs: TimestampMs = Timestamp.fromNumber(123);
const entry: NodeEntryJson = {
title: "Include key",
key: "much-different-key",
};
const expected: NodeEntry = {
title: "Include key",
key: "much-different-key",
contributors: [],
timestampMs,
weight: null,
};
expect(normalizeNodeEntry(entry, timestampMs)).toEqual(expected);
});
});
describe("_titleSlug", () => {
it("should handle example titles", () => {
const expected: {[string]: string} = {
"should-be-lowercased": "Should-be-LowerCased",
"special-characters-as-dashes": "Special@$Characters #As$dashes",
"no-starting-trailing-dashes": "-No starting / trailing dashes-",
"no-duplicate-dashes": "No - Duplicate -%- Dashes",
};
const actual = Object.keys(expected).map((k) => _titleSlug(expected[k]));
expect(actual).toEqual(Object.keys(expected));
});
});
});