describe.test.ts

6.9 KB · raw

1import { describe, expect, test } from "bun:test";2import { describeFile, describeFolder, firstSentence } from "../src/describe/describe.ts";34describe("descriptions by kind (§21.5)", () => {5	test("Zig and Rust take the leading //! lines, not /// or //", () => {6		const zig = '//! Tree rendering.\n//!\n//! Second paragraph.\n\nconst std = @import("std");\n';7		expect(describeFile("src/tree.zig", zig)).toBe("Tree rendering.\n\nSecond paragraph.");8		const rust = "// Copyright 2026 June\n\n//! Parses things.\nuse std::io;\n";9		expect(describeFile("src/lib.rs", rust)).toBe("Parses things.");10		expect(describeFile("a.zig", "// main.zig — entry point\nconst std = 1;\n")).toBeNull();11		expect(describeFile("a.rs", "/// Doc of a function.\nfn main() {}\n")).toBeNull();12	});1314	test("V, Go, C, C++, Swift, Kotlin, Java, JS and TS take the leading // block", () => {15		for (const ext of "v go c h cpp swift kt java js ts".split(" ")) {16			expect(describeFile(`x.${ext}`, "// Reads the bucket.\n// Two lines.\n\ncode();\n")).toBe(17				"Reads the bucket.\nTwo lines.",18			);19		}20		expect(describeFile("cli.v", "//! Command line parsing.\n\nmodule cli\n")).toBe(21			"Command line parsing.",22		);23	});2425	test("the /** */ block counts, with its stars and delimiters removed", () => {26		const ts = "/**\n * Markup that is `safe`.\n *\n * More.\n */\nexport class Html {}\n";27		expect(describeFile("html.ts", ts)).toBe("Markup that is `safe`.\n\nMore.");28		expect(describeFile("x.java", "/** One line. */\nclass X {}\n")).toBe("One line.");29	});3031	test("a comment after the first code does not count", () => {32		expect(describeFile("x.kt", "val x = 1\n/** Too late. */\nobject X\n")).toBeNull();33		expect(describeFile("x.go", "package main\n// Too late.\n")).toBeNull();34		expect(describeFile("x.ts", "import x from 'y';\n// Too late.\n")).toBeNull();35	});3637	test("Kotlin, Java and Swift pass over package and import lines first", () => {38		const kt =39			"// SPDX-License-Identifier: MIT\npackage com.peach\n\nimport a.B\n\n/**\n * Idle auto-lock timer.\n */\nclass T\n";40		expect(describeFile("T.kt", kt)).toBe("Idle auto-lock timer.");41		expect(describeFile("X.java", "package a;\nimport b.C;\n// Reads C.\nclass X {}\n")).toBe(42			"Reads C.",43		);44		expect(describeFile("X.swift", "import Foundation\n\n/// A view.\nstruct X {}\n")).toBe(45			"A view.",46		);47	});4849	test("Go build constraints are not a description", () => {50		const go = "//go:build linux\n\n// Package tty talks to terminals.\npackage tty\n";51		expect(describeFile("tty.go", go)).toBe("Package tty talks to terminals.");52	});5354	test("Python takes the module docstring, after comments and a shebang", () => {55		const py =56			'#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n"""Sync tool.\n\n    Indented body.\n"""\nimport os\n';57		expect(describeFile("tool.py", py)).toBe("Sync tool.\n\nIndented body.");58		expect(describeFile("one.py", "'''One line.'''\n")).toBe("One line.");59		expect(describeFile("x.py", 'import os\n"""Not a docstring."""\n')).toBeNull();60		expect(describeFile("x.py", "# Just a comment.\nimport os\n")).toBeNull();61	});6263	test("Shell, Nix, TOML and YAML take the leading # lines after any shebang", () => {64		for (const ext of "sh nix toml yaml".split(" ")) {65			expect(describeFile(`x.${ext}`, "# Builds the thing.\n# Quickly.\nvalue = 1\n")).toBe(66				"Builds the thing.\nQuickly.",67			);68		}69		expect(describeFile("install.sh", "#!/bin/sh\n# Installs kepr.\nset -e\n")).toBe(70			"Installs kepr.",71		);72		expect(describeFile("bin/run", "#!/usr/bin/env bash\n\n# Runs it.\nexec x\n")).toBe("Runs it.");73		expect(describeFile("install.sh", "#!/bin/sh\nset -e\n# Too late.\n")).toBeNull();74		expect(describeFile("bin/tool", "#!/usr/bin/env python3\n# Not shell.\n")).toBeNull();75	});7677	test("Markdown takes the first paragraph, not a heading", () => {78		const md = "# Guide\n\nRead **this** first,\nthen the rest.\n\nMore.\n";79		expect(describeFile("docs/guide.md", md)).toBe("Read **this** first,\nthen the rest.");80		expect(describeFile("empty.md", "# Only a heading\n")).toBeNull();81	});8283	test("no comment, an unknown kind or a binary file means no description", () => {84		expect(describeFile("x.ts", "export const one = 1;\n")).toBeNull();85		expect(describeFile("x.ts", "")).toBeNull();86		expect(describeFile("logo.png", "// looks like a comment\n")).toBeNull();87		expect(describeFile("x.c", "//\n//\nint x;\n")).toBeNull();88	});89});9091describe("license headers", () => {92	test("the first block is skipped when it carries SPDX, copyright or licence wording", () => {93		const headers = [94			"// SPDX-License-Identifier: MIT\n",95			"/*\n * Copyright (c) 2026 June\n */\n",96			"// Licensed under the Apache License, Version 2.0.\n// See LICENSE.\n",97			"/* © 2026 Asha. All rights reserved. */\n",98		];99		for (const header of headers) {100			expect(describeFile("x.ts", `${header}\n// Real description.\ncode();\n`)).toBe(101				"Real description.",102			);103			expect(describeFile("x.ts", `${header}\ncode();\n`)).toBeNull();104		}105		expect(describeFile("x.nix", "# SPDX-License-Identifier: MIT\n\n# The flake.\n{ }\n")).toBe(106			"The flake.",107		);108	});109110	test("only the first block, so a description mentioning a licence survives", () => {111		expect(describeFile("x.ts", "// Checks each repo's license file.\ncode();\n")).toBe(112			"Checks each repo's license file.",113		);114		const two = "// Copyright 2026.\n\n// Copyright notices, rendered.\ncode();\n";115		expect(describeFile("x.ts", two)).toBe("Copyright notices, rendered.");116	});117});118119describe("folders", () => {120	test("a README's first paragraph wins over the module file", () => {121		const children = [122			{ name: "mod.zig", text: "//! From the module.\n" },123			{ name: "README.md", text: "# Pages\n\nFrom the readme.\n" },124		];125		expect(describeFolder(children)).toBe("From the readme.");126	});127128	test("else the module file's header, else nothing", () => {129		expect(describeFolder([{ name: "mod.rs", text: "//! Rust module.\n" }])).toBe("Rust module.");130		expect(describeFolder([{ name: "index.ts", text: "// TS module.\nx();\n" }])).toBe(131			"TS module.",132		);133		expect(describeFolder([{ name: "__init__.py", text: '"""Py package."""\n' }])).toBe(134			"Py package.",135		);136		expect(describeFolder([{ name: "other.ts", text: "// Not a module file.\n" }])).toBeNull();137	});138});139140describe("the one-line description", () => {141	test("joins hard-wrapped lines and stops at the first sentence", () => {142		expect(firstSentence("Reads the\nbucket. Then more.\n\nNext paragraph.")).toBe(143			"Reads the bucket.",144		);145	});146147	test("ignores full stops inside code, versions and common abbreviations", () => {148		expect(firstSentence("Reads `koh.build`, e.g. v3.0.72 files. More.")).toBe(149			"Reads `koh.build`, e.g. v3.0.72 files.",150		);151		expect(firstSentence("`a. b` is code! Next.")).toBe("`a. b` is code!");152	});153154	test("a paragraph without a full stop is the whole line", () => {155		expect(firstSentence("Command line entry: dispatch table")).toBe(156			"Command line entry: dispatch table",157		);158	});159});