diff --git a/src/core/github.js b/src/core/github.js index fe4b77fc59..62afa9b4ab 100644 --- a/src/core/github.js +++ b/src/core/github.js @@ -153,6 +153,7 @@ export async function run(conf) { repoURL: ghURL.href, apiBase: githubAPI, fullName: `${org}/${repo}`, + commitHistoryURL: commitHistoryURL.href, }; resolveGithubPromise(normalizedGHObj); diff --git a/src/w3c/headers.js b/src/w3c/headers.js index b2898f37a8..1a208fcc05 100644 --- a/src/w3c/headers.js +++ b/src/w3c/headers.js @@ -267,7 +267,7 @@ function validateDateAndRecover(conf, prop, fallbackDate = new Date()) { return new Date(ISODate.format(new Date())); } -export function run(conf) { +export async function run(conf) { if (!conf.specStatus) { const msg = docLink`Missing required configuration: ${"[specStatus]"}.`; const hint = docLink`Please select an appropriate status from ${"[specStatus]"} based on your W3C group. If in doubt, use \`"unofficial"\`.`; @@ -506,6 +506,7 @@ export function run(conf) { conf.publishISODate = conf.publishDate.toISOString(); conf.shortISODate = ISODate.format(conf.publishDate); validatePatentPolicies(conf); + await deriveHistoryURI(conf); // configuration done - yay! @@ -697,6 +698,36 @@ export function run(conf) { }); } +async function deriveHistoryURI(conf) { + if (!conf.shortName || conf.historyURI === null) { + return; // Nothing to do + } + + const historyURL = new URL( + conf.historyURI ?? conf.shortName, + "https://www.w3.org/standards/history/" + ); + + // If it's on the Rec Track or it's TR worthy, then it has a history. + const willHaveHistory = [...recTrackStatus, ...W3CNotes, ...maybeRecTrack]; + if (willHaveHistory.includes(conf.specStatus)) { + conf.historyURI = historyURL.href; + return; + } + + // Do a fetch HEAD request to see if the history exists... + // We don't discriminate... if it's on the W3C website with a history, + // we show it. + try { + const response = await fetch(historyURL, { method: "HEAD" }); + if (response.ok) { + conf.historyURI = response.url; + } + } catch { + // Ignore fetch errors + } +} + function validatePatentPolicies(conf) { if (!conf.wgPatentPolicy) return; const policies = new Set([].concat(conf.wgPatentPolicy)); diff --git a/src/w3c/templates/headers.js b/src/w3c/templates/headers.js index 1ca09ba76d..b0d742458a 100644 --- a/src/w3c/templates/headers.js +++ b/src/w3c/templates/headers.js @@ -183,6 +183,7 @@ export default (conf, options) => {
${conf.edDraftURI}
` : ""} + ${renderHistory(conf)} ${conf.testSuiteURI ? html`
${l10n.test_suite}
@@ -273,6 +274,28 @@ export default (conf, options) => { `; }; +function renderHistory(conf) { + if (!conf.historyURI && !conf.github) return; + const ddElements = []; + if (conf.historyURI) { + const dd = html`
+ ${l10n.publication_history} +
`; + ddElements.push(dd); + } + if (conf.github) { + const dd = html` +
+ ${l10n.commit_history} +
+ `; + ddElements.push(dd); + } + + return html`
${l10n.history}
+ ${ddElements}`; +} + function renderSpecTitle(conf) { const specType = conf.isCR ? conf.longStatus : conf.textStatus; const preamble = conf.prependW3C diff --git a/tests/spec/w3c/headers-spec.js b/tests/spec/w3c/headers-spec.js index 76bcb00897..d363ebc9c7 100644 --- a/tests/spec/w3c/headers-spec.js +++ b/tests/spec/w3c/headers-spec.js @@ -8,6 +8,8 @@ import { makeStandardOps, } from "../SpecHelper.js"; +import { recTrackStatus } from "../../../src/w3c/headers.js"; + const findContent = string => { return ({ textContent }) => textContent.trim() === string; }; @@ -287,9 +289,6 @@ describe("W3C — Headers", () => { expect(dd.nextElementSibling.nextElementSibling.textContent).toContain( "FORMER EDITOR 3" ); - expect( - dd.nextElementSibling.nextElementSibling.nextElementSibling - ).toBeNull(); }); }); it("takes a single editors into account", async () => { @@ -2253,6 +2252,121 @@ describe("W3C — Headers", () => { }); }); + describe("History", () => { + it("shows the publication history of the spec", async () => { + const ops = makeStandardOps({ shortName: "test", specStatus: "WD" }); + const doc = await makeRSDoc(ops); + const [history] = contains(doc, ".head dt", "History:"); + expect(history).toBeTruthy(); + expect(history.nextElementSibling).toBeTruthy(); + const historyLink = history.nextElementSibling.querySelector("a"); + expect(historyLink).toBeTruthy(); + expect(historyLink.href).toBe( + "https://www.w3.org/standards/history/test" + ); + expect(historyLink.textContent).toContain("Publication history"); + }); + + it("includes a dd for the commit history of the document", async () => { + const ops = makeStandardOps({ + github: "w3c/respec", + shortName: "test", + specStatus: "WD", + }); + const doc = await makeRSDoc(ops); + const [commitHistory] = contains(doc, ".head dd>a", "Commit history"); + expect(commitHistory.href).toBe("https://github.com/w3c/respec/commits/"); + const [publicationHistory] = contains( + doc, + ".head dd>a", + "Publication history" + ); + expect(publicationHistory.href).toBeTruthy( + "https://www.w3.org/standards/history/respec" + ); + }); + + it("includes a dd for the commit history, but excludes a publication history for unpublished types", async () => { + for (const specStatus of ["unofficial", "base"]) { + const ops = makeStandardOps({ + github: "my/some-repo", + shortName: "test", + specStatus, + }); + const doc = await makeRSDoc(ops); + const [commitHistory] = contains(doc, ".head dd>a", "Commit history"); + expect(commitHistory).withContext(specStatus).toBeTruthy(); + const publicationHistory = contains( + doc, + ".head dd>a", + "Publication history" + ); + expect(publicationHistory.length).withContext(specStatus).toBe(0); + } + }); + + it("allows overriding the historyURI", async () => { + const ops = makeStandardOps({ + shortName: "test", + specStatus: "WD", + historyURI: "http://example.com/history", + }); + const doc = await makeRSDoc(ops); + const [history] = contains(doc, ".head dt", "History:"); + expect(history).toBeTruthy(); + expect(history.nextElementSibling).toBeTruthy(); + const historyLink = history.nextElementSibling.querySelector("a"); + expect(historyLink).toBeTruthy(); + expect(historyLink.href).toBe("http://example.com/history"); + }); + + it("allowing removing the history entirely buy nulling it out", async () => { + const ops = makeStandardOps({ + shortName: "test", + specStatus: "WD", + historyURI: null, + }); + const doc = await makeRSDoc(ops); + const [history] = contains(doc, ".head dt", "History:"); + expect(history).toBeFalsy(); + }); + + it("derives the historyURI automatically when it's missing, but the document is on TR", async () => { + const ops = makeStandardOps({ + shortName: "payment-request", + specStatus: "ED", + }); + const doc = await makeRSDoc(ops); + const [history] = contains(doc, ".head dt", "History:"); + expect(history).toBeTruthy(); + expect(history.nextElementSibling).toBeTruthy(); + const historyLink = history.nextElementSibling.querySelector("a"); + expect(historyLink).toBeTruthy(); + expect(historyLink.href).toBe( + "https://www.w3.org/standards/history/payment-request" + ); + }); + + it("includes the history for all rec-track status docs", async () => { + for (const specStatus of recTrackStatus) { + const shortName = `${specStatus}-test`; + const ops = makeStandardOps({ + shortName, + specStatus, + }); + const doc = await makeRSDoc(ops); + const [history] = contains(doc, ".head dt", "History:"); + expect(history).withContext(specStatus).toBeTruthy(); + expect(history.nextElementSibling).withContext(specStatus).toBeTruthy(); + const historyLink = history.nextElementSibling.querySelector("a"); + expect(historyLink).withContext(specStatus).toBeTruthy(); + expect(historyLink.href) + .withContext(specStatus) + .toBe(`https://www.w3.org/standards/history/${shortName}`); + } + }); + }); + describe("Add Preview Status in title", () => { it("when document title is present", async () => { const ops = makeStandardOps();