Skip to content

Commit

Permalink
Test that Qiskit TocGrouping is in sync with index page
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric-Arellano committed May 10, 2024
1 parent 29b8c60 commit 460dc1c
Showing 1 changed file with 133 additions and 0 deletions.
133 changes: 133 additions & 0 deletions scripts/lib/api/TocGrouping.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// This code is a Qiskit project.
//
// (C) Copyright IBM 2024.
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE file in the root directory
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
//
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

import { readFile } from "fs/promises";

import { expect, test } from "@jest/globals";

import { QISKIT_TOC_GROUPING } from "./TocGrouping";
import type { TocEntry } from "./generateToc";

/**
* The module names belonging to a section, e.g.
* `['qiskit.circuit', 'qiskit.circuit.library']`.
*
* For top-level modules, like `qiskit.quantum_info`, there will only
* be a single element.
* */
type ModuleGroup = string[];

/**
* Ensure our assumptions about Qiskit's TocGrouping are correct.
*
* These assumptions are what allow us to infer what is a top-level module
* (like 'qiskit.quantum_info') vs. a section (like 'Circuit construction').
*
* If these assumptions are getting in the way, you can rewrite these tests.
* A more robust approach would be to read the front-matter/metadata for the
* URLs to see if `python_api_type: module` is set. This is complicated by
* module pages sometimes being in 'Module overview' vs being on a standalone page
* like 'qiskit.circuit.singleton'.
*/
function validateTopLevelModuleAssumptions(): void {
for (const entry of QISKIT_TOC_GROUPING.entries) {
if (
entry.kind === "module" &&
!entry.title.includes("qiskit.") &&
entry.title !== "API index"
) {
throw new Error(
"Expected every top-level module of QISKIT_TOC_GROUPING to have the module name in " +
"its title, e.g. 'Quantum information (qiskit.quantum_info)'. This will break the " +
"tests in this file. Either add the module name to the title or rewrite these tests. " +
`Bad entry: ${entry.title}`,
);
} else if (entry.kind === "section" && entry.name.includes("qiskit.")) {
throw new Error(
"Expected very `section` of QISKIT_TOC_GROUPING.entries to not have 'qiskit.' in the " +
"name. This will break the tests in this file. Either remove the module name or " +
`rewrite these tests. Bad entry: ${entry.name}`,
);
}
}
}

function extractModuleName(text: string): string {
const re = /qiskit\.[a-zA-Z._0-9]+/;
// Ex: 'Quantum information (qiskit.quantum_info)'.
// Ex: '* [Quantum Circuits (`qiskit.circuit`)](circuit)'
const match = text.match(re);
if (!match) {
throw new Error(`Could not extract module from ${text}`);
}
return match[0];
}

/**
* Finds all groups of modules from the index page.
*
* Each group has a list of page titles with the module name in parantheses.
*/
async function getIndexModuleGroups(fp: string): Promise<ModuleGroup[]> {
const rawIndex = await readFile(fp, "utf-8");
const result: ModuleGroup[] = [];
let currentGroup: ModuleGroup = [];
for (const line of rawIndex.split("\n")) {
if (line.startsWith("* ")) {
if (line.includes("qiskit.")) {
const module = extractModuleName(line);
currentGroup.push(module);
}
continue;
} else if (currentGroup.length) {
result.push(currentGroup);
currentGroup = [];
}
}
return result;
}

async function getTocModuleGroups(fp: string): Promise<ModuleGroup[]> {
const rawToc = await readFile(fp, "utf-8");
const entries = JSON.parse(rawToc).children as TocEntry[];
const result: ModuleGroup[] = [];
for (const entry of entries) {
const isTopLevelModule = entry.title.includes("qiskit.");
if (isTopLevelModule) {
const moduleName = extractModuleName(entry.title);
result.push([moduleName]);
} else if (entry.children) {
// The modules inside a custom Section cannot be renamed, so they
// will have their title set as the module, e.g. `qiskit.circuit`.
const childrenModules = Array.from(
entry.children
.filter((child) => child.title.startsWith("qiskit."))
.map((child) => child.title),
);
if (childrenModules.length) {
result.push(childrenModules);
}
}
}
return result;
}

test("Qiskit ToC mirrors index page sections", async () => {
validateTopLevelModuleAssumptions();
const indexModuleGroups = await getIndexModuleGroups(
"docs/api/qiskit/dev/index.mdx",
);
const tocModuleGroups = await getTocModuleGroups(
"docs/api/qiskit/dev/_toc.json",
);
expect(indexModuleGroups).toEqual(tocModuleGroups);
});

0 comments on commit 460dc1c

Please sign in to comment.