test.js
const assert = require('assert');
const child_process = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
const util = require('util');
const { Sequelize } = require('sequelize')
const ourbigbook = require('./index')
const ourbigbook_nodejs = require('./nodejs');
const ourbigbook_nodejs_front = require('./nodejs_front');
const ourbigbook_nodejs_webpack_safe = require('./nodejs_webpack_safe');
const { read_include } = require('./web_api');
const models = require('./models');
const {
assert_xpath,
xpath_header,
xpath_header_split,
xpath_header_parent,
} = require('./test_lib')
const MAKE_GIT_REPO_PRE_EXEC = [
['git', ['init']],
['git', ['add', '.']],
['git', ['commit', '-m', '0']],
['git', ['remote', 'add', 'origin', 'git@github.com:ourbigbook/ourbigbook-generate.git']],
]
const PATH_SEP = ourbigbook.Macro.HEADER_SCOPE_SEPARATOR
// Common default convert options for the tests.
const convert_opts = {
add_test_instrumentation: true,
body_only: true,
split_headers: false,
// Can help when debugging failures.
log: {
//'ast-inside': true,
//parse: true,
//'split-headers': true,
//'tokens-inside': true,
//tokenize': true,
}
};
// assert_lib helper for Ast tests.
function assert_lib_ast(
description,
stdin,
assert_ast,
options={}
) {
options.stdin = stdin
options.assert_ast = assert_ast
return assert_lib(description, options)
}
/** THE ASSERT EVERYTHING ENTRYPOINT for library based tests.
*
* For full CLI tests, use assert_cli instead.
*
* assert_lib uses very minimal mocking, so tests are highly meaningful.
*
* Its interface is highly compatible with assert_cli, in many cases you can just
* switch between the two freely by just converting assert_cli 'args' to the corresponding
*
* and assert_lib generally preferred
* as it runs considerably faster.
*/
function assert_lib(
description,
options={}
) {
it('lib: ' + description, async function () {
options = Object.assign({}, options);
if (!('assert_ast' in options)) {
// Assert that this given Ast subset is present in the output.
// Only considers the content argument of the toplevel node for convenience.
options.assert_ast = undefined;
}
if (!('assert_xpath_stdout' in options)) {
// Like assert_xpath, but for the input coming from options.stdin.
// This is analogous to stdout output on the CLI.
options.assert_xpath_stdout = [];
}
if (!('assert_not_xpath_stdout' in options)) {
options.assert_not_xpath_stdout = [];
}
if (!('assert_xpath' in options)) {
// Assert xpath on other outputs besides the main output.
// These can come either from split headers, from from separate
// files via convert_before.
options.assert_xpath = {};
}
if (!('assert_not_xpath' in options)) {
// Like assert_xpath but assert it does not match.
options.assert_not_xpath = {};
}
//if (!('assert_bigb_stdout' in options)) {
// options.assert_bigb_stdout = undefined;
//}
if (!('assert_bigb' in options)) {
options.assert_bigb = {};
}
if (!('convert_before' in options)) {
// List of strings. Convert files at these paths from default_file_reader
// before the main conversion to build up the cross-file reference database.
options.convert_before = [];
}
if (!('convert_before_norender' in options)) {
options.convert_before_norender = [];
}
if (!('convert_dir' in options)) {
// Convert all OurBigBook input files in the directory as in `ourbigbook .` from the CLI.
// First do an extract IDs pass, and then a render pass just like for the CLI.
// This option overrides both convert_before and convert_before_norender.
// You generally just want to use this option always.
options.convert_dir = false;
}
if (!('convert_opts' in options)) {
// Extra convert options on top of the default ones
// to be passed to ourbigbook.convert.
options.convert_opts = {};
}
if (!('duplicate_ids' in options)) {
options.duplicate_ids = []
}
if (!('filesystem' in options)) {
// Passed to ourbigbook.convert.
options.filesystem = default_filesystem;
}
if (!('has_error' in options)) {
// Has error somewhere, but our precise error line/column assertions
// are failing, and we are lazy to fix them right now. But still it is better
// to know that it does not blow up with an exception, and has at least.
// one error message.
options.has_error = false;
}
if (!('stdin' in options)) {
// A string to be converted directly. Input can also be provided via filesystem:
// through our mock filesystem. This option is somewhat analogous to stdin input on CLI.
// This is the preferred approach for when the filename doesn't matter to the test.
options.stdin = undefined
}
if (!('invalid_title_titles' in options)) {
options.invalid_title_titles = []
}
if (!('assert_not_exists' in options)) {
options.assert_not_exists = [];
}
if (!('path_sep' in options.convert_opts)) {
options.convert_opts.path_sep = PATH_SEP;
}
if (!('read_include' in options.convert_opts)) {
options.convert_opts.read_include = read_include({
exists: (inpath) => inpath in options.filesystem,
read: (inpath) => options.filesystem[inpath],
path_sep: PATH_SEP,
})
}
options.convert_opts.fs_exists_sync = (my_path) => {
return options.filesystem.hasOwnProperty(my_path) || filesystem_dirs.hasOwnProperty(my_path)
}
options.convert_opts.read_file = (readpath, context) => {
if (readpath in filesystem_dirs) {
return {
type: 'directory',
}
} else {
return {
type: 'file',
content: options.filesystem[readpath],
}
}
}
let filesystem = options.filesystem
const filesystem_dirs = {'': {}}
for (f in filesystem) {
do {
f = path.dirname(f)
if (f === '.')
break
filesystem_dirs[f] = {}
} while (true)
}
// Add directory to filesystem so that exist checks won't blow up.
for (let p in filesystem) {
do {
p = path.dirname(p)
filesystem[p] = undefined
} while (p !== '.')
}
if (options.stdin !== undefined) {
if (!('input_path_noext' in options) && options.convert_opts.split_headers) {
options.input_path_noext = ourbigbook.INDEX_BASENAME_NOEXT;
}
const main_input_path = options.input_path_noext + '.' + ourbigbook.OURBIGBOOK_EXT
assert(!(main_input_path in options.filesystem))
filesystem = Object.assign({}, filesystem)
filesystem[main_input_path] = options.stdin
}
let convert_before, convert_before_norender
if (options.convert_dir) {
convert_before_norender = Object.keys(filesystem).filter((inpath) => path.parse(inpath).ext === '.' + ourbigbook.OURBIGBOOK_EXT)
convert_before = convert_before_norender
} else {
convert_before_norender = options.convert_before_norender
convert_before = options.convert_before
}
// Convenience parameter that sets both input_path_noext and toplevel_id.
// options.input_path_noext
if (!('toplevel' in options)) {
options.toplevel = false;
}
const new_convert_opts = Object.assign({}, convert_opts);
Object.assign(new_convert_opts, options.convert_opts);
if (options.toplevel) {
new_convert_opts.body_only = false;
}
if (Object.keys(options.assert_bigb).length || 'assert_bigb_stdout' in options) {
new_convert_opts.output_format = ourbigbook.OUTPUT_FORMAT_OURBIGBOOK
}
// SqlDbProvider with in-memory database.
const sequelize = await ourbigbook_nodejs_webpack_safe.create_sequelize({
storage: ':memory:',
logging: false,
},
Sequelize,
{ force: true },
)
let exception
try {
const db_provider = new ourbigbook_nodejs_webpack_safe.SqlDbProvider(sequelize);
new_convert_opts.db_provider = db_provider
const rendered_outputs = {}
async function convert(input_path, render) {
//console.error({input_path});
const extra_returns = {};
assert(input_path in filesystem)
const input_string = filesystem[input_path];
const dependency_convert_opts = Object.assign({}, new_convert_opts);
dependency_convert_opts.input_path = input_path;
dependency_convert_opts.toplevel_id = path.parse(input_path).name;
dependency_convert_opts.render = render;
await ourbigbook.convert(input_string, dependency_convert_opts, extra_returns);
Object.assign(rendered_outputs, extra_returns.rendered_outputs)
if (extra_returns.errors.length !== 0) {
console.error(extra_returns.errors.join('\n'));
assert.strictEqual(extra_returns.errors.length, 0)
}
await ourbigbook_nodejs_webpack_safe.update_database_after_convert({
extra_returns,
db_provider,
is_render_after_extract: render && convert_before_norender_set.has(input_path),
sequelize,
path: input_path,
render,
})
}
const convert_before_norender_set = new Set(convert_before_norender)
for (const input_path of convert_before_norender) {
await convert(input_path, false)
}
const check_db_error_messages = await ourbigbook_nodejs_webpack_safe.check_db(sequelize, convert_before_norender)
if (check_db_error_messages.length > 0) {
console.error(check_db_error_messages.join('\n'))
assert.strictEqual(check_db_error_messages.length, 0);
}
for (const input_path of convert_before) {
await convert(input_path, true)
}
//console.error('main');
if (options.stdin === undefined) {
if (options.input_path_noext !== undefined) throw new Error('input_string === undefined && input_path_noext !== undefined')
if (options.assert_xpath_stdout.length) throw new Error('input_string === undefined && options.assert_xpath_stdout !== []')
if (options.assert_not_xpath_stdout.length) throw new Error('input_string === undefined && options.assert_not_xpath_stdout !== []')
} else {
if (options.input_path_noext !== undefined) {
new_convert_opts.input_path = options.input_path_noext + '.' + ourbigbook.OURBIGBOOK_EXT;
new_convert_opts.toplevel_id = options.input_path_noext;
}
const extra_returns = {};
const output = await ourbigbook.convert(options.stdin, new_convert_opts, extra_returns);
Object.assign(rendered_outputs, extra_returns.rendered_outputs)
if (new_convert_opts.input_path !== undefined) {
await ourbigbook_nodejs_webpack_safe.update_database_after_convert({
extra_returns,
db_provider,
sequelize,
path: new_convert_opts.input_path,
render: true,
})
}
// Post conversion checks.
const has_subset_extra_returns = { fail_reason: '' };
let is_subset;
let content;
let content_array;
if (options.assert_bigb_stdout) {
assert.strictEqual(output, options.assert_bigb_stdout);
}
if (options.assert_ast === undefined) {
is_subset = true;
} else {
if (options.toplevel) {
content = extra_returns.ast;
content_array = [content]
is_subset = ast_has_subset(content, options.assert_ast, has_subset_extra_returns);
} else {
content = extra_returns.ast.args.content;
content_array = content
is_subset = ast_arg_has_subset(content, options.assert_ast, has_subset_extra_returns);
}
}
const expect_error_precise =
options.error_line !== undefined ||
options.error_column !== undefined ||
options.error_path !== undefined ||
options.error_message !== undefined;
const expect_error = expect_error_precise || options.has_error;
if (
!is_subset ||
(
!expect_error &&
extra_returns.errors.length !== 0
)
) {
// Too verbose to show by default.
//console.error('tokens:');
//console.error(JSON.stringify(extra_returns.tokens, null, 2));
//console.error();
//console.error('ast output:');
//console.error(JSON.stringify(content, null, 2));
//console.error();
if (options.assert_ast !== undefined) {
console.error('ast output toString:');
console.error(content_array.map(c => c.toString()).join('\n'));
console.error();
console.error('ast expect:');
console.error(JSON.stringify(options.assert_ast, null, 2));
console.error();
console.error('errors:');
}
for (const error of extra_returns.errors) {
console.error(error);
}
if (!is_subset) {
console.error('failure reason:');
console.error(has_subset_extra_returns.fail_reason);
console.error();
}
for (const error of extra_returns.errors) {
console.error(error.toString());
}
console.error('input ' + util.inspect(options.stdin));
assert.strictEqual(extra_returns.errors.length, 0);
assert.ok(is_subset);
}
if (expect_error) {
assert.ok(extra_returns.errors.length > 0);
const error = extra_returns.errors[0];
if (expect_error_precise) {
assert.deepStrictEqual(
error.source_location,
new ourbigbook.SourceLocation(
options.error_line,
options.error_column,
options.error_path
)
);
if (options.error_message) {
assert.strictEqual(error.message, options.error_message)
}
}
}
for (const xpath_expr of options.assert_xpath_stdout) {
assert_xpath(xpath_expr, output);
}
for (const xpath_expr of options.assert_not_xpath_stdout) {
assert_xpath(xpath_expr, output, { count: 0 });
}
}
for (const key in options.assert_bigb) {
assert.strictEqual(rendered_outputs[key].full, options.assert_bigb[key], `bigb output different than expected for "${key}"`);
}
for (const key in options.assert_xpath) {
const output = rendered_outputs[key];
assert.notStrictEqual(output, undefined, `missing output path "${key}", existing: ${Object.keys(rendered_outputs)}`);
for (const xpath_expr of options.assert_xpath[key]) {
assert_xpath(xpath_expr, output.full, { message: key, stdout: false });
}
}
for (const key in options.assert_not_xpath) {
const output = rendered_outputs[key];
assert.notStrictEqual(output, undefined);
for (const xpath_expr of options.assert_not_xpath[key]) {
assert_xpath(xpath_expr, output.full, {
count: 0,
message: key,
stdout: false,
});
}
}
for (const key of options.assert_not_exists) {
assert.ok(!(key in rendered_outputs))
}
} catch(e) {
exception = e
}
await ourbigbook_nodejs_webpack_safe.destroy_sequelize(sequelize)
if (exception) {
throw exception
}
})
}
/** Similar to assert_lib, but allow input not coming from files, analogous to stdin. */
function assert_lib_stdin(description, input, options) {
assert_lib_ast(description, input, undefined, options)
}
function assert_db_checks(actual_rows, expects) {
for (let i = 0; i < actual_rows.length; i++) {
const actual_row = actual_rows[i]
const expect = expects[i]
const ast = ourbigbook.AstNode.fromJSON(actual_row.ast_json)
const source_location = ast.source_location
assert.strictEqual(actual_row.idid, expect[0])
assert.strictEqual(actual_row.path, expect[1])
assert.strictEqual(source_location.line, expect[2])
assert.strictEqual(source_location.column, expect[3])
}
assert.strictEqual(actual_rows.length, expects.length)
}
function assert_equal(description, output, expected_output) {
it(description, ()=>{assert.strictEqual(output, expected_output);});
}
/** Assert that the conversion fails in a controlled way, giving correct
* error line and column as the first error, and without throwing an
* exception. Ideally, we should assert all errors. However, asserting
* the first one correctly is the most critical part of it, because
* errors can compound up, making later errors meaningless. So the message
* only has to be 100% correct on the first error pointed out, to allow
* the user to deterministically solve that problem first, and then move
* on to the next. */
function assert_lib_error(description, input, line, column, path, options={}) {
const new_convert_opts = Object.assign({}, options);
new_convert_opts.error_line = line;
new_convert_opts.error_column = column;
new_convert_opts.error_path = path;
assert_lib_ast(
description,
input,
undefined,
new_convert_opts
);
}
const testdir = path.join(__dirname, ourbigbook_nodejs_webpack_safe.TMP_DIRNAME, 'test')
fs.rmSync(testdir, { recursive: true, force: true });
fs.mkdirSync(testdir, { recursive: true });
// Test the ourbigbook executable via a separate child process call.
//
// The test runs in a clean temporary directory. If the test fails,
// the directory is cleaned up, so you can list the latest directory
// with:
//
// ls -crtl /tmp
//
// and then inspect it interactively to debug.
function assert_cli(
description,
options={}
) {
it('cli: ' + description, async function () {
options = Object.assign({}, options);
if (!('args' in options)) {
options.args = [];
}
if (!('assert_bigb' in options)) {
options.assert_bigb = {};
}
if (!('assert_exists' in options)) {
options.assert_exists = [];
}
if (!('assert_exists_sqlite' in options)) {
options.assert_exists_sqlite = [];
}
if (!('assert_exit_status' in options)) {
options.assert_exit_status = 0;
}
if (!('assert_not_exists' in options)) {
options.assert_not_exists = [];
}
if (!('assert_not_xpath' in options)) {
options.assert_not_xpath = {};
}
if (!('assert_xpath_stdout' in options)) {
options.assert_xpath_stdout = [];
}
if (!('assert_xpath' in options)) {
options.assert_xpath = {};
}
if (!('cwd' in options)) {
options.cwd = '.';
}
if (!('filesystem' in options)) {
options.filesystem = {};
}
if (!('pre_exec' in options)) {
options.pre_exec = [];
}
if ('timeout' in options) {
// https://stackoverflow.com/questions/15971167/how-to-increase-timeout-for-a-single-test-case-in-mocha
this.timeout(options.timeout);
}
const tmpdir = path.join(testdir, this.test.title.toLowerCase().replace(/[^a-z0-9]+/g, '-'));
// These slighty modified titles should still be unique, but who knows.
// Not modifying them would require cd quoting.
assert(!fs.existsSync(tmpdir));
fs.mkdirSync(tmpdir);
const cwd = path.relative(process.cwd(), path.join(tmpdir, options.cwd))
if (!fs.existsSync(cwd)) {
fs.mkdirSync(cwd);
}
update_filesystem(options.filesystem, tmpdir)
process.env.PATH = process.cwd() + ':' + process.env.PATH
const common_args = ['--add-test-instrumentation', '--fakeroot', tmpdir]
if (ourbigbook_nodejs_front.postgres) {
// Clear the database.
const sequelize = await ourbigbook_nodejs_webpack_safe.create_sequelize({
logging: false,
},
Sequelize,
{ force: true },
)
await ourbigbook_nodejs_webpack_safe.destroy_sequelize(sequelize)
}
for (const entry of options.pre_exec) {
if (entry.filesystem_update) {
update_filesystem(entry.filesystem_update, tmpdir)
}
let cmd, args, status = 0;
if (Array.isArray(entry)) {
;[cmd, args] = entry
} else if (entry.cmd) {
;[cmd, args] = entry.cmd
if (entry.status !== undefined) {
status = entry.status
}
}
if (cmd !== undefined) {
if (cmd === 'ourbigbook') {
args = common_args.concat(args)
}
const out = child_process.spawnSync(cmd, args, {cwd: cwd});
assert.strictEqual(out.status, status, 'bad exit status\n' + exec_assert_message(out, cmd, args, cwd));
}
}
const cmd = 'ourbigbook'
const args = common_args.concat(options.args)
const out = child_process.spawnSync(cmd, args, {
cwd: cwd,
input: options.stdin,
});
const assert_msg = exec_assert_message(out, cmd, args, cwd);
assert.strictEqual(out.status, options.assert_exit_status, assert_msg);
for (const xpath_expr of options.assert_xpath_stdout) {
assert_xpath(
xpath_expr,
out.stdout.toString(ourbigbook_nodejs_webpack_safe.ENCODING),
{message: assert_msg},
);
}
for (const relpath in options.assert_xpath) {
const assert_msg_xpath = `path should match xpath: ${relpath}\n\n` + assert_msg;
const fullpath = path.join(tmpdir, relpath);
assert.ok(fs.existsSync(fullpath), `path does not exist: ${fullpath}\n\n` + assert_msg);
const html = fs.readFileSync(fullpath).toString(ourbigbook_nodejs_webpack_safe.ENCODING);
for (const xpath_expr of options.assert_xpath[relpath]) {
assert_xpath(xpath_expr, html, {message: assert_msg_xpath});
}
}
for (const relpath in options.assert_not_xpath) {
const assert_msg_xpath = `path should not match xpath: ${relpath}\n\n` + assert_msg;
const fullpath = path.join(tmpdir, relpath);
assert.ok(fs.existsSync(fullpath), assert_msg_xpath);
const html = fs.readFileSync(fullpath).toString(ourbigbook_nodejs_webpack_safe.ENCODING);
for (const xpath_expr of options.assert_not_xpath[relpath]) {
assert_xpath(xpath_expr, html, { message: assert_msg_xpath, count: 0 });
}
}
for (const relpath of options.assert_exists) {
const fullpath = path.join(tmpdir, relpath);
assert.ok(fs.existsSync(fullpath), exec_assert_message(
out, cmd, args, cwd, 'path should exist: ' + relpath));
}
for (const relpath in options.assert_bigb) {
const assert_msg_bigb = `path should contain: ${relpath}\n\n` + assert_msg;
const fullpath = path.join(tmpdir, relpath);
assert.ok(fs.existsSync(fullpath), `path does not exist: ${fullpath}`);
const content = fs.readFileSync(fullpath).toString(ourbigbook_nodejs_webpack_safe.ENCODING);
assert.strictEqual(options.assert_bigb[relpath], content, assert_msg_bigb);
}
if (!ourbigbook_nodejs_front.postgres) {
for (const relpath of options.assert_exists_sqlite) {
const fullpath = path.join(tmpdir, relpath);
assert.ok(fs.existsSync(fullpath), exec_assert_message(
out, cmd, args, cwd, 'path should exist: ' + relpath));
}
}
for (const relpath of options.assert_not_exists) {
const fullpath = path.join(tmpdir, relpath);
assert.ok(!fs.existsSync(fullpath), exec_assert_message(
out, cmd, args, cwd, 'path should not exist: ' + relpath));
}
});
}
/** Determine if a given Ast argument has a subset.
*
* For each lement of the array, only the subset of each object is checked.
*
* @param {Array[AstNode]} unmodified array of AstNode as output by convert
* @param {Array[Object]} lightweight AstNode notation containing only built-in JavaScript objects
* such as dict, array and string, to make writing tests a bit less verbose.
* @return {Bool} true iff ast_subset is a subset of this node
*/
function ast_arg_has_subset(arg, subset, extra_returns) {
if (arg.length() !== subset.length) {
extra_returns.fail_reason = `arg.length !== subset.length ${arg.length()} ${subset.length}
arg: ${JSON.stringify(arg, null, 2)}
subset: ${JSON.stringify(subset, null, 2)}
`;
return false;
}
for (let i = 0; i < arg.length(); i++) {
if (!ast_has_subset(arg.get(i), subset[i], extra_returns))
return false;
}
return true;
}
/** See: ast_arg_has_subset. */
function ast_has_subset(ast, ast_subset, extra_returns) {
for (const ast_subset_prop_name in ast_subset) {
if (!(ast_subset_prop_name in ast)) {
extra_returns.fail_reason = `!(ast_subset_prop_name in ast: ${ast_subset_prop_name} ${ast_subset_prop_name}`;
return false
}
const ast_prop = ast[ast_subset_prop_name];
const ast_subset_prop = ast_subset[ast_subset_prop_name];
if (ast_subset_prop_name === 'args') {
for (const ast_subset_arg_name in ast_subset_prop) {
if (!(ast_subset_arg_name in ast_prop)) {
extra_returns.fail_reason = `!(ast_subset_arg_name in ast_prop): ${ast_subset_prop_name} ${ast_subset_arg_name}`;
return false;
}
if (!ast_arg_has_subset(ast_prop[ast_subset_arg_name], ast_subset_prop[ast_subset_arg_name], extra_returns))
return false;
}
} else {
if (ast_prop !== ast_subset_prop) {
extra_returns.fail_reason = `ast_prop !== ast_subset_prop: '${ast_subset_prop_name}' '${ast_prop}' '${ast_subset_prop}'`;
return false;
}
}
}
return true;
}
/** Shortcut to create node with a 'content' argument for ast_arg_has_subset.
*
* @param {Array} the argument named content, which is very common across macros.
* If undefined, don't add a content argument at all.
*/
function a(macro_name, content, extra_args={}, extra_props={}) {
let args = extra_args;
if (content !== undefined) {
args.content = content;
}
return Object.assign(
{
'macro_name': macro_name,
'args': args,
},
extra_props
);
}
// TODO we should get rid of this overbloated mess.
const default_filesystem = {
'include-one-level-1.bigb': `= cc
dd
`,
'include-one-level-2.bigb': `= ee
ff
`,
'include-two-levels.bigb': `= ee
ff
== gg
hh
`,
'include-two-levels-parent.bigb': `= Include two levels parent
h1 content
= Include two levels parent h2
{parent=include-two-levels-parent}
h2 content
`,
'include-two-levels-subdir/index.bigb': `= Include two levels subdir h1
== Include two levels subdir h2
`,
'include-with-error.bigb': `= bb
\\reserved_undefined
`,
'include-circular-1.bigb': `= bb
\\Include[include-circular-2]
`,
'include-circular-2.bigb': `= cc
\\Include[include-circular-1]
`,
}
// Saner version of default_filesystem to which we can slowly migrate.
const default_filesystem2 = {
'include-one-level-1.bigb': `= Include one level 1
Include one level 1 paragraph.
`,
'include-one-level-2.bigb': `= Include one level 2
Include one level 2 paragraph.
`,
}
function exec_assert_message(out, cmd, args, cwd, msg_extra) {
let ret = ''
if (msg_extra !== undefined) {
ret = msg_extra + '\n\n'
}
ret += `cmd: cd ${cwd} && ${cmd} ${args.join(' ')}
stdout:
${out.stdout.toString(ourbigbook_nodejs_webpack_safe.ENCODING)}
stderr:
${out.stderr.toString(ourbigbook_nodejs_webpack_safe.ENCODING)}`;
return ret;
}
function header_file_about_ast(path, type='file') {
return a('P', [
t(`This section is about the ${type}: `),
a('b', [
a('a', undefined, {href: [t(path)]})
]),
])
}
/** Shortcut to create plaintext nodes for ast_arg_has_subset, we have too many of those. */
function t(text) { return {'macro_name': 'plaintext', 'text': text}; }
function update_filesystem(filesystem, tmpdir) {
for (const relpath in filesystem) {
const file_content = filesystem[relpath]
const file_path = path.join(tmpdir, relpath)
if (
// This special value means deletion.
file_content === null
) {
fs.unlinkSync(file_path)
} else {
// This is the string that will be written to the file.
const dirpath = path.join(tmpdir, path.parse(relpath).dir);
fs.mkdirSync(dirpath, { recursive: true });
fs.writeFileSync(file_path, file_content);
}
}
}
// Empty document.
assert_lib_ast('empty document', '', []);
// Paragraphs.
assert_lib_ast('one paragraph implicit no split headers', 'ab\n',
[a('P', [t('ab')])],
)
assert_lib_ast('one paragraph explicit', '\\P[ab]\n',
[a('P', [t('ab')])],
)
assert_lib_ast('two paragraphs', 'p1\n\np2\n',
[
a('P', [t('p1')]),
a('P', [t('p2')]),
]
)
assert_lib_ast('three paragraphs',
'p1\n\np2\n\np3\n',
[
a('P', [t('p1')]),
a('P', [t('p2')]),
a('P', [t('p3')]),
]
)
assert_lib_ast('insane paragraph at start of sane quote',
'\\Q[\n\naa]\n',
[
a('Q', [
a('P', [t('aa')])]
),
]
)
assert_lib_ast('sane quote without inner paragraph',
'\\Q[aa]\n',
[a('Q', [t('aa')])],
)
assert_lib_error('paragraph three newlines', 'p1\n\n\np2\n', 3, 1);
assert_lib_error('paragraph three newlines', 'p1\n\n\np2\n', 3, 1);
assert_lib_ast('one newline at the end of document is ignored', 'p1\n', [a('P', [t('p1')])]);
assert_lib_error('two newlines at the end of document are an error', 'p1\n\n', 1, 3);
assert_lib_error('three newline at the end of document an error', 'p1\n\n\n', 2, 1);
// List.
const l_with_explicit_ul_expect = [
a('P', [t('ab')]),
a('Ul', [
a('L', [t('cd')]),
a('L', [t('ef')]),
]),
a('P', [t('gh')]),
];
assert_lib_ast('l with explicit ul and no extra spaces',
`ab
\\Ul[\\L[cd]\\L[ef]]
gh
`,
l_with_explicit_ul_expect
)
assert_lib_ast('l with implicit ul sane',
`ab
\\L[cd]
\\L[ef]
gh
`,
l_with_explicit_ul_expect
)
assert_lib_ast('l with implicit ul insane',
`ab
* cd
* ef
gh
`,
l_with_explicit_ul_expect
)
assert_lib_ast('empty insane list item without a space',
`* ab
*
* cd
`,
[
a('Ul', [
a('L', [t('ab')]),
a('L', []),
a('L', [t('cd')]),
]),
]
)
assert_lib_ast('l with explicit ul and extra spaces',
`ab
\\Ul[
\\L[cd]\u0020
\u0020\t\u0020
\\L[ef]
]
gh
`,
l_with_explicit_ul_expect
)
assert_lib_ast('ordered list',
`ab
\\Ol[
\\L[cd]
\\L[ef]
]
gh
`,
[
a('P', [t('ab')]),
a('Ol', [
a('L', [t('cd')]),
a('L', [t('ef')]),
]),
a('P', [t('gh')]),
]
)
assert_lib_ast('list with paragraph sane',
`\\L[
aa
bb
]
`,
[
a('Ul', [
a('L', [
a('P', [t('aa')]),
a('P', [t('bb')]),
]),
]),
]
)
assert_lib_ast('list with paragraph insane',
`* aa
bb
`,
[
a('Ul', [
a('L', [
a('P', [t('aa')]),
a('P', [t('bb')]),
]),
]),
]
)
assert_lib_ast('list with multiline paragraph insane',
`* aa
bb
cc
`,
[
a('Ul', [
a('L', [
a('P', [t('aa')]),
a('P', [t('bb\ncc')]),
]),
]),
]
)
// https://github.com/ourbigbook/ourbigbook/issues/54
assert_lib_ast('insane list with literal no error',
`* aa
\`\`
bb
cc
\`\`
`,
[
a('Ul', [
a('L', [
a('P', [t('aa')]),
a('C', [t('bb\ncc')]),
]),
]),
]
)
assert_lib_error('insane list with literal with error',
`* aa
\`\`
bb
cc
\`\`
`,
4, 1
)
assert_lib_ast('insane list with literal with double newline is not an error',
`* aa
\`\`
bb
cc
\`\`
`,
[
a('Ul', [
a('L', [
a('P', [t('aa')]),
a('C', [t('bb\n\ncc')]),
]),
]),
]
)
// https://github.com/ourbigbook/ourbigbook/issues/53
assert_lib_ast('insane list with element with newline separated arguments',
`* aa
\`\`
bb
\`\`
{id=cc}
`,
[
a('Ul', [
a('L', [
a('P', [t('aa')]),
a('C', [t('bb')], {id: [t('cc')]}),
]),
]),
]
)
assert_lib_ast('insane list inside paragraph',
`aa
* bb
* cc
dd
`,
[
a('P', [
t('aa'),
a('Ul', [
a('L', [t('bb')]),
a('L', [t('cc\n')]),
]),
t('dd'),
]),
]
)
assert_lib_ast('insane list at start of positional argument with newline',
`\\Q[
* bb
* cc
]
`,
[
a('Q', [
a('Ul', [
a('L', [t('bb')]),
// TODO get rid of that newline
// https://github.com/ourbigbook/ourbigbook/issues/245
a('L', [t('cc\n')]),
]),
]),
]
)
assert_lib_ast('insane list at start of positional argument without newline',
`\\Q[* bb
* cc
]
`,
[
a('Q', [
a('Ul', [
a('L', [t('bb')]),
a('L', [t('cc\n')]),
]),
]),
]
)
assert_lib_ast('insane list at end of positional argument without newline',
`\\Q[
* bb
* cc]
`,
[
a('Q', [
a('Ul', [
a('L', [t('bb')]),
a('L', [t('cc')]),
]),
]),
]
)
assert_lib_ast('insane list at start of named argument with newline',
`\\Image[http://example.com]
{description=
* bb
* cc
}
`,
[
a('Image', undefined, {
description: [
a('Ul', [
a('L', [t('bb')]),
a('L', [t('cc\n')]),
]),
],
}),
]
)
assert_lib_ast('insane list at start of named argument without newline',
`\\Image[http://example.com]
{description=* bb
* cc
}
`,
[
a('Image', undefined, {
description: [
a('Ul', [
a('L', [t('bb')]),
a('L', [t('cc\n')]),
]),
],
}),
]
)
//assert_lib_ast('insane list at end of named argument without newline',
// // TODO https://github.com/ourbigbook/ourbigbook/issues/246
// `\\Image[http://example.com]
//{description=
//* bb
//* cc}
//`,
// [
// a('Image', undefined, {
// description: [
// a('Ul', [
// a('L', [t('bb')]),
// a('L', [t('cc')]),
// ]),
// ],
// }),
// ]
//);
assert_lib_ast('nested list insane',
`* aa
* bb
`,
[
a('Ul', [
a('L', [
t('aa'),
a('Ul', [
a('L', [
t('bb')
]),
]),
]),
]),
]
)
assert_lib_ast('escape insane list at start of document',
'\\* a',
[a('P', [t('* a')])],
)
assert_lib_ast('escape insane list after a newline',
`a
\\* b`,
[a('P', [t('a\n* b')])],
)
assert_lib_ast('escape insane list inside list indent',
`* a
\\* b`,
[
a('Ul', [
a('L', [
t('a\n* b'),
]),
]),
]
)
assert_lib_ast('asterisk in the middle of line does not need to be escaped',
'a * b',
[a('P', [t('a * b')])],
)
// https://github.com/ourbigbook/ourbigbook/issues/81
assert_lib_ast('insane list immediately inside insane list',
`* * aa
* bb
* cc
`,
[
a('Ul', [
a('L', [
a('Ul', [
a('L', [t('aa')]),
a('L', [t('bb')]),
a('L', [t('cc')]),
]),
]),
]),
]
)
// Table.
const tr_with_explicit_table_expect = [
a('P', [t('ab')]),
a('Table', [
a('Tr', [
a('Th', [t('cd')]),
a('Th', [t('ef')]),
]),
a('Tr', [
a('Td', [t('00')]),
a('Td', [t('01')]),
]),
a('Tr', [
a('Td', [t('10')]),
a('Td', [t('11')]),
]),
]),
a('P', [t('gh')]),
];
assert_lib_ast('tr with explicit table',
`ab
\\Table[
\\Tr[
\\Th[cd]
\\Th[ef]
]
\\Tr[
\\Td[00]
\\Td[01]
]
\\Tr[
\\Td[10]
\\Td[11]
]
]
gh
`,
tr_with_explicit_table_expect
)
assert_lib_ast('tr with implicit table',
`ab
\\Tr[
\\Th[cd]
\\Th[ef]
]
\\Tr[
\\Td[00]
\\Td[01]
]
\\Tr[
\\Td[10]
\\Td[11]
]
gh
`,
tr_with_explicit_table_expect
)
assert_lib_ast('fully implicit table',
`ab
|| cd
|| ef
| 00
| 01
| 10
| 11
gh
`,
tr_with_explicit_table_expect
)
assert_lib_ast('insane table inside insane list inside insane table',
`| 00
| 01
* l1
* l2
| 20
| 21
| 30
| 31
| 10
| 11
`,
[
a('Table', [
a('Tr', [
a('Td', [t('00')]),
a('Td', [
a('P', [t('01')]),
a('Ul', [
a('L', [t('l1')]),
a('L', [
a('P', [t('l2')]),
a('Table', [
a('Tr', [
a('Td', [t('20')]),
a('Td', [t('21')]),
]),
a('Tr', [
a('Td', [t('30')]),
a('Td', [t('31')]),
]),
]),
]),
]),
]),
]),
a('Tr', [
a('Td', [t('10')]),
a('Td', [t('11')]),
]),
]),
]
)
// https://github.com/ourbigbook/ourbigbook/issues/81
assert_lib_ast('insane table immediately inside insane list',
`* | 00
| 01
| 10
| 11
`,
[
a('Ul', [
a('L', [
a('Table', [
a('Tr', [
a('Td', [t('00')]),
a('Td', [t('01')]),
]),
a('Tr', [
a('Td', [t('10')]),
a('Td', [t('11')]),
]),
]),
]),
])
]
)
assert_lib_ast('insane table body with empty cell and no space',
`| 00
|
| 02
`, [
a('Table', [
a('Tr', [
a('Td', [t('00')]),
a('Td', []),
a('Td', [t('02')]),
]),
]),
],
)
assert_lib_ast('insane table head with empty cell and no space',
`|| 00
||
|| 02
`, [
a('Table', [
a('Tr', [
a('Th', [t('00')]),
a('Th', []),
a('Th', [t('02')]),
]),
]),
],
)
assert_lib_ast('implicit table escape', '\\| a\n',
[a('P', [t('| a')])],
)
assert_lib_ast("pipe space in middle of line don't need escape", 'a | b\n',
[a('P', [t('a | b')])],
)
assert_lib_ast('auto_parent consecutive implicit tr and l',
`\\Tr[\\Td[ab]]
\\L[cd]
`,
[
a('P', [
a('Table', [
a('Tr', [
a('Td', [t('ab')]),
]),
]),
a('Ul', [
a('L', [t('cd')]),
]),
]),
]
)
assert_lib_ast('table with id has caption',
`\\Table{id=ab}
[
| 00
| 01
]
`,
[
a('Table', [
a('Tr', [
a('Td', [t('00')]),
// TODO get rid of the \n.
a('Td', [t('01\n')]),
]),
], {}, { id: 'ab' }),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Table 1']",
]
}
)
assert_lib_ast('table with title has caption',
`\\Table{title=a b}
[
| 00
| 01
]
`,
[
a('Table', [
a('Tr', [
a('Td', [t('00')]),
// TODO get rid of the \n.
a('Td', [t('01\n')]),
]),
], {}, { id: 'table-a-b' }),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Table 1']",
]
}
)
assert_lib_ast('table with description has caption',
`\\Table{description=a b}
[
| 00
| 01
]
`,
[
a('Table', [
a('Tr', [
a('Td', [t('00')]),
// TODO get rid of the \n.
a('Td', [t('01\n')]),
]),
], {}, { id: '_1' }),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Table 1']",
]
}
)
assert_lib_ast('table without id, title, nor description does not have caption',
`\\Table[
| 00
| 01
]
`,
[
a('Table', [
a('Tr', [
a('Td', [t('00')]),
a('Td', [t('01\n')]),
]),
]),
],
{
assert_not_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Table 1']",
]
}
)
assert_lib_ast('table without id, title, nor description does not increment the table count',
`\\Table{id=0}[
| 00
| 01
]
\\Table[
| 10
| 11
]
\\Table{id=1}[
| 20
| 21
]
`,
[
a('Table', [
a('Tr', [
a('Td', [t('00')]),
a('Td', [t('01\n')]),
]),
]),
a('Table', [
a('Tr', [
a('Td', [t('10')]),
a('Td', [t('11\n')]),
]),
]),
a('Table', [
a('Tr', [
a('Td', [t('20')]),
a('Td', [t('21\n')]),
]),
]),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Table 1']",
"//x:span[@class='caption-prefix' and text()='Table 2']",
],
assert_not_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Table 3']",
],
},
)
// Images.
assert_lib_ast('image: block simple',
`ab
\\Image[cd]
gh
`,
[
a('P', [t('ab')]),
a('Image', undefined, {src: [t('cd')]}),
a('P', [t('gh')]),
],
{
filesystem: { cd: '' },
assert_xpath_stdout: [
`//x:a[@href='${ourbigbook.RAW_PREFIX}/cd']//x:img[@src='${ourbigbook.RAW_PREFIX}/cd']`,
],
},
)
assert_lib_ast('image: inline simple',
`ab
\\image[cd]
gh
`,
[
a('P', [t('ab')]),
a('P', [a('image', undefined, {src: [t('cd')]})] ),
a('P', [t('gh')]),
],
{
filesystem: { cd: '' },
assert_xpath_stdout: [
`//x:img[@src='${ourbigbook.RAW_PREFIX}/cd']`,
],
},
)
assert_lib_ast('image: link argument',
`ab
\\Image[cd]{link=http://example.com}
gh
`,
[
a('P', [t('ab')]),
a('Image', undefined, {src: [t('cd')]}),
a('P', [t('gh')]),
],
{
filesystem: { cd: '' },
assert_xpath_stdout: [
`//x:a[@href='http://example.com']//x:img[@src='${ourbigbook.RAW_PREFIX}/cd']`,
],
},
)
assert_lib_ast('video: simple',
`ab
\\Video[cd]
gh
`,
[
a('P', [t('ab')]),
a('Video', undefined, {src: [t('cd')]}),
a('P', [t('gh')]),
],
{
filesystem: { cd: '' },
assert_xpath_stdout: [
`//x:video[@src='${ourbigbook.RAW_PREFIX}/cd']`,
],
},
)
assert_lib_ast('image: title',
`\\Image[ab]{title=c d}`,
[
a('Image', undefined, {
src: [t('ab')],
title: [t('c d')],
}),
],
{ filesystem: { ab: '' } },
)
assert_lib_error('image: unknown provider',
`\\Image[ab]{provider=reserved_undefined}`,
1, 11
)
assert_lib_error('image: provider that does not match actual source',
`\\Image[https://upload.wikimedia.org/wikipedia/commons/5/5b/Gel_electrophoresis_insert_comb.jpg]{provider=local}`,
1, 96
)
assert_lib_stdin('image: provider that does match actual source',
`\\Image[https://upload.wikimedia.org/wikipedia/commons/5/5b/Gel_electrophoresis_insert_comb.jpg]{provider=wikimedia}`,
)
assert_lib_ast('image: image with id has caption',
`\\Image[aa]{id=bb}{external}\n`,
[
a('Image', undefined, {
src: [t('aa')],
id: [t('bb')],
}),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Figure 1']",
]
}
)
assert_lib_ast('image: image with title has caption',
`\\Image[aa]{title=b b}{external}\n`,
[
a('Image', undefined, {
src: [t('aa')],
title: [t('b b')],
}, {}, { id: 'b-b' }),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Figure 1']",
]
}
)
assert_lib_ast('image: image with description has caption',
`\\Image[aa]{description=b b}{external}\n`,
[
a('Image', undefined, {
src: [t('aa')],
description: [t('b b')],
}, {}, { id: '_1' }),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Figure 1']",
]
}
)
assert_lib_ast('image: image with source has caption',
`\\Image[aa]{source=b b}{external}\n`,
[
a('Image', undefined, {
src: [t('aa')],
source: [t('b b')],
}, {}, { id: '_1' }),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Figure 1']",
]
}
)
assert_lib_ast('image: image without id, title, description nor source does not have caption',
`\\Image[aa]{external}
`,
[
a('Image', undefined, {
src: [t('aa')],
}, {}, { id: '_1' }),
],
{
assert_not_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Figure 1']",
]
}
)
assert_lib_ast('image: image without id, title, description nor source does not increment the image count',
`\\Image[aa]{id=aa}{external}
\\Image[bb]{external}
\\Image[cc]{id=cc}{external}
`,
[
a('Image', undefined, { src: [t('aa')], }, {}, { id: 'aa' }),
a('Image', undefined, { src: [t('bb')], }, {}, { id: '_2' }),
a('Image', undefined, { src: [t('cc')], }, {}, { id: 'cc' }),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Figure 1']",
"//x:span[@class='caption-prefix' and text()='Figure 2']",
],
assert_not_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Figure 3']",
],
},
)
assert_lib_ast('image: image title with x to header in another file',
`\\Image[aa]{title=My \\x[notindex]}{external}`,
[
a('Image', undefined, { src: [t('aa')], }, {}, { id: 'my-notindex-h1' }),
],
{
convert_before: ['notindex.bigb'],
filesystem: {
'notindex.bigb': `= notindex h1
`,
},
}
)
assert_lib('link to image in other files that has title with x to header in another file',
{
convert_dir: true,
filesystem: {
'index.bigb': `= Index
\\x[image-my-notindex]
`,
'image.bigb': `= image h1
\\Image[aa]{title=My \\x[notindex]}{external}
`,
'notindex.bigb': `= notindex h1
`,
},
assert_xpath: {
'index.html': [
"//x:a[@href='image.html#image-my-notindex' and text()='Figure \"My notindex h1\"']",
],
},
}
)
assert_lib('link to image in other files that has title with x to synonym header in another file',
{
convert_dir: true,
filesystem: {
'index.bigb': `= Index
\\x[image-my-notindex-h1-2]
`,
'image.bigb': `= image h1
\\Image[aa]{title=My \\x[notindex-h1-2]}{external}
`,
'notindex.bigb': `= notindex h1
= notindex h1 2
{synonym}
`,
},
assert_xpath: {
'index.html': [
"//x:a[@href='image.html#image-my-notindex-h1-2' and text()='Figure \"My notindex h1 2\"']",
],
},
}
)
assert_lib('link to image in other files that has title with two x to other headers',
// check_db extra ID removal was removing the first ID because the link line/columns were the same for both,
// fixed at title= argument position, and not at the \x position.
{
convert_dir: true,
filesystem: {
'index.bigb': `= Index
\\x[image-my-notindex-2-notindex-3]
`,
'notindex.bigb': `= Notindex
\\Image[aa]{title=My \\x[notindex-2] \\x[notindex-3]}{external}
== Notindex 2
== Notindex 3
`,
},
assert_xpath: {
'index.html': [
"//x:a[@href='notindex.html#image-my-notindex-2-notindex-3' and text()='Figure \"My notindex 2 notindex 3\"']",
],
},
}
)
assert_lib('image: dot added automatically between title and description if title does not end in punctuation',
{
convert_dir: true,
filesystem: {
'index.bigb': `\\Image[http://a]
{title=My title 1}
{description=My image 1.}
\\Image[http://a]
{title=My title 2.}
{description=My image 2.}
\\Image[http://a]
{title=My title 3?}
{description=My image 3.}
\\Image[http://a]
{title=My title 4!}
{description=My image 4.}
\\Image[http://a]
{title=My title 5 (2000)}
{description=My image 5.}
\\Image[http://a]
{title=My title with source 1}
{description=My image with source 1.}
{source=http://example.com}
\\Image[http://a]
{title=My title with source 2.}
{description=My image with source 2.}
{source=http://example.com}
\\Video[http://a]
{title=My title 1}
{description=My video 1.}
\\Video[http://a]
{title=My title 2.}
{description=My video 2.}
\`\`
f()
\`\`
{title=My title 1}
{description=My code 1.}
\`\`
f()
\`\`
{title=My title 2.}
{description=My code 2.}
\\Table[
| a
| b
]
{title=My title 1}
{description=My table 1.}
\\Table[
| a
| b
]
{title=My title 2.}
{description=My table 2.}
\\Q[To be]
{title=My title 1}
{description=My quote 1.}
\\Q[To be]
{title=My title 2.}
{description=My quote 2.}
\\Image[http://a]
{description=My image no title.}
\\Image[http://a]
{description=My image source no title.}
{source=http://example.com}
`
},
assert_xpath: {
'index.html': [
"//x:figcaption[text()='. My title 1. My image 1.']",
"//x:figcaption[text()='. My title 2. My image 2.']",
"//x:figcaption[text()='. My title 3? My image 3.']",
"//x:figcaption[text()='. My title 4! My image 4.']",
"//x:figcaption[text()='. My title 5 (2000) My image 5.']",
"//x:figcaption[text()='. My title 1. My video 1.']",
"//x:figcaption[text()='. My title 2. My video 2.']",
// TODO any way to test this properly? I would like something like:
//"//x:figcaption[text()='. My title with source 2. . My image with source 2.']",
// There are multiple text nodes because of the <a from source in the middle.
"//x:figcaption[text()='. My title with source 1. ']",
"//x:figcaption[text()='. My title with source 2. ']",
"//x:div[@class='caption' and text()='. My title 1. My code 1.']",
"//x:div[@class='caption' and text()='. My title 2. My code 2.']",
"//x:div[@class='caption' and text()='. My title 1. My table 1.']",
"//x:div[@class='caption' and text()='. My title 2. My table 2.']",
"//x:div[@class='caption' and text()='. My title 1. My quote 1.']",
"//x:div[@class='caption' and text()='. My title 2. My quote 2.']",
"//x:figcaption[text()='. My image no title.']",
"//x:figcaption[text()='. My image source no title.']",
],
},
}
)
// Escapes.
assert_lib_ast('escape backslash', 'a\\\\b\n', [a('P', [t('a\\b')])]);
assert_lib_ast('escape left square bracket', 'a\\[b\n', [a('P', [t('a[b')])]);
assert_lib_ast('escape right square bracket', 'a\\]b\n', [a('P', [t('a]b')])]);
assert_lib_ast('escape left curly brace', 'a\\{b\n', [a('P', [t('a{b')])]);
assert_lib_ast('escape right curly brace', 'a\\}b\n', [a('P', [t('a}b')])]);
assert_lib_ast('escape header id', `= tmp
\\x["'\\<>&]
== tmp 2
{id="'\\<>&}
`,
undefined,
{
assert_xpath_stdout: [
"//*[@id=concat('\"', \"'<>&\")]",
],
}
)
// Positional arguments.
// Has no content argument.
assert_lib_ast('p with no content argument', '\\P\n', [a('P')]);
assert_lib_ast('table with no content argument', '\\Table\n', [a('Table')]);
// Has empty content argument.
assert_lib_ast('p with empty content argument', '\\P[]\n', [a('P', [])]);
// Named arguments.
assert_lib_ast('p with id before', '\\P{id=ab}[cd]\n',
[a('P', [t('cd')], {id: [t('ab')]})]);
assert_lib_ast('p with id after', '\\P[cd]{id=ab}\n',
[a('P', [t('cd')], {id: [t('ab')]})]);
// https://github.com/ourbigbook/ourbigbook/issues/101
assert_lib_error('named argument given multiple times',
'\\P[ab]{id=cd}{id=ef}', 1, 14);
assert_lib_error(
'non-empty named argument without = is an error',
'\\P{id ab}[cd]',
1, 6, 'notindex.bigb',
{
input_path_noext: 'notindex',
}
)
assert_lib_error(
'named argument: open bracket at end of file fails gracefully',
'\\P[ab]{',
1, 7, 'notindex.bigb',
{
input_path_noext: 'notindex',
}
)
assert_lib_ast('empty named argument without = is allowed',
'\\Image[img.png]{description=}{external}\n',
[a('Image', undefined, {
src: [t('img.png')],
description: [],
})]
)
// Newline after close.
assert_lib_ast('text after block element',
`a
\\C[
b
c
]
d
e
`,
[
a('P', [t('a')]),
a('P', [
a('C', [t('b\nc')]),
t('\nd'),
]),
a('P', [t('e')]),
]
)
assert_lib_ast('macro after block element',
`a
\\C[
b
c
]
\\c[d]
e
`,
[
a('P', [t('a')]),
a('P', [
a('C', [t('b\nc')]),
t('\n'),
a('c', [t('d')]),
]),
a('P', [t('e')]),
]
)
// Literal arguments.
assert_lib_ast('literal argument code inline',
'\\c[[\\ab[cd]{ef}]]\n',
[a('P', [a('c', [t('\\ab[cd]{ef}')])])],
)
assert_lib_ast('literal argument code block',
`a
\\C[[
\\[]{}
\\[]{}
]]
d
`,
[
a('P', [t('a')]),
a('C', [t('\\[]{}\n\\[]{}')]),
a('P', [t('d')]),
],
)
assert_lib_ast('non-literal argument leading and trailing newline get removed',
`\\P[
a
b
]
`,
[a('P', [t('a\nb')])],
)
assert_lib_ast('literal argument leading and trailing newlines get removed',
`\\P[[
a
b
]]
`,
[a('P', [t('a\nb')])],
)
assert_lib_ast('literal argument leading and trailing newlines get removed but not the second one',
`\\P[[
a
b
]]
`,
[a('P', [t('\na\nb\n')])],
)
assert_lib_ast('literal agument escape leading open no escape',
'\\c[[\\ab]]\n',
[a('P', [a('c', [t('\\ab')])])],
)
assert_lib_ast('literal agument escape leading open one backslash',
'\\c[[\\[ab]]\n',
[a('P', [a('c', [t('[ab')])])],
)
assert_lib_ast('literal agument escape leading open two backslashes',
'\\c[[\\\\[ab]]\n',
[a('P', [a('c', [t('\\[ab')])])],
)
assert_lib_ast('literal agument escape trailing close no escape',
'\\c[[\\]]\n',
[a('P', [a('c', [t('\\')])])],
)
assert_lib_ast('literal agument escape trailing one backslash',
'\\c[[\\]]]\n',
[a('P', [a('c', [t(']')])])],
)
assert_lib_ast('literal agument escape trailing two backslashes',
'\\c[[\\\\]]]\n',
[a('P', [a('c', [t('\\]')])])],
)
// Newline between arguments.
const newline_between_arguments_expect = [
a('C', [t('ab')], {id: [t('cd')]}),
];
assert_lib_ast('not literal argument with argument after newline',
`\\C[
ab
]
{id=cd}
`,
newline_between_arguments_expect
)
assert_lib_ast('yes literal argument with argument after newline',
`\\C[[
ab
]]
{id=cd}
`,
newline_between_arguments_expect
)
assert_lib_ast('yes insane literal argument with argument after newline',
`\`\`
ab
\`\`
{id=cd}
`,
newline_between_arguments_expect
)
// Links.
// \a
assert_lib_ast('link: simple to external URL',
'a \\a[http://example.com][example link] b\n',
[
a('P', [
t('a '),
a('a', [t('example link')], {'href': [t('http://example.com')]}),
t(' b'),
]),
]
)
assert_lib_ast('link: auto sane',
'a \\a[http://example.com] b\n',
[
a('P', [
t('a '),
a('a', undefined, {'href': [t('http://example.com')]}),
t(' b'),
]),
]
)
assert_lib_ast('link: auto insane space start and end',
'a http://example.com b\n',
[
a('P', [
t('a '),
a('a', undefined, {'href': [t('http://example.com')]}),
t(' b'),
]),
]
)
assert_lib_ast('link: simple to local file that exists',
'a \\a[local-path.txt] b\n',
[
a('P', [
t('a '),
a('a', undefined, {'href': [t('local-path.txt')]}),
t(' b'),
]),
],
{ filesystem: { 'local-path.txt': '' } }
)
assert_lib_error('link: simple to local file that does not exist give an error without external',
'a \\a[local-path.txt] b\n',
1, 5,
)
assert_lib_stdin('link: simple to local file that does not exist does not give an error with external',
'a \\a[local-path.txt]{external} b\n',
)
assert_lib_ast('link: auto insane start end document',
'http://example.com',
[a('P', [a('a', undefined, {'href': [t('http://example.com')]})])],
)
assert_lib_ast('link: auto insane start end square brackets',
'\\P[http://example.com]\n',
[a('P', [a('a', undefined, {'href': [t('http://example.com')]})])],
)
assert_lib_ast('link: auto insane with alpha character before it',
'ahttp://example.com',
[a('P', [
t('a'),
a('a', undefined, {'href': [t('http://example.com')]})
])]
)
assert_lib_ast('link: auto insane with literal square brackets around it',
'\\[http://example.com\\]\n',
[a('P', [
t('['),
a('a', undefined, {'href': [t('http://example.com]')]})
])]
)
assert_lib_ast('link: auto insane can be escaped with a backslash',
'\\http://example.com\n',
[a('P', [t('http://example.com')])],
)
assert_lib_ast('link: auto insane is not a link if the domain is empty at eof',
'http://\n',
[a('P', [t('http://')])],
)
assert_lib_ast('link: auto insane is not a link if the domain is empty at space',
'http:// a\n',
[a('P', [t('http:// a')])],
)
assert_lib_ast('link: auto insane start end named argument',
'\\Image[aaa.jpg]{description=http://example.com}\n',
[a('Image', undefined, {
description: [a('a', undefined, {'href': [t('http://example.com')]})],
src: [t('aaa.jpg')],
})],
{ filesystem: { 'aaa.jpg': '' } }
)
assert_lib_ast('link: auto insane start end named argument',
'\\Image[aaa.jpg]{source=http://example.com}\n',
[a('Image', undefined, {
source: [t('http://example.com')],
src: [t('aaa.jpg')],
})],
{ filesystem: { 'aaa.jpg': '' } }
)
assert_lib_ast('link: auto insane newline',
`a
http://example.com
b
`,
[
a('P', [t('a')]),
a('P', [a('a', undefined, {'href': [t('http://example.com')]})]),
a('P', [t('b')]),
]
)
assert_lib_ast('link: insane with custom body no newline',
'http://example.com[aa]',
[
a('P', [
a('a', [t('aa')], {'href': [t('http://example.com')]}),
]),
]
)
assert_lib_ast('link: insane with custom body with newline',
'http://example.com\n[aa]',
[
a('P', [
a('a', [t('aa')], {'href': [t('http://example.com')]}),
]),
]
)
assert_lib_ast('link: auto end in space',
`a http://example.com b`,
[
a('P', [
t('a '),
a('a', undefined, {'href': [t('http://example.com')]}),
t(' b'),
])
]
)
assert_lib_ast('link: auto end in square bracket',
`\\P[a http://example.com]`,
[
a('P', [
t('a '),
a('a', undefined, {'href': [t('http://example.com')]}),
])
]
)
assert_lib_ast('link: auto containing escapes',
`\\P[a http://example.com\\]a\\}b\\\\c\\ d]`,
[
a('P', [
t('a '),
a('a', undefined, {'href': [t('http://example.com]a}b\\c d')]}),
])
]
)
assert_lib_ast('link: auto sane http https removal',
'\\a[http://example.com] \\a[https://example.com] \\a[ftp://example.com]',
[
a('P', [
a('a', undefined, {'href': [t('http://example.com')]}),
t(' '),
a('a', undefined, {'href': [t('https://example.com')]}),
t(' '),
a('a', undefined, {'href': [t('ftp://example.com')]}),
]),
],
{
assert_xpath_stdout: [
"//x:a[@href='http://example.com' and text()='example.com']",
"//x:a[@href='https://example.com' and text()='example.com']",
"//x:a[@href='ftp://example.com' and text()='ftp://example.com']",
]
}
)
assert_lib_ast('link: auto insane http https removal',
'http://example.com https://example.com',
[
a('P', [
a('a', undefined, {'href': [t('http://example.com')]}),
t(' '),
a('a', undefined, {'href': [t('https://example.com')]}),
]),
],
{
assert_xpath_stdout: [
"//x:a[@href='http://example.com' and text()='example.com']",
"//x:a[@href='https://example.com' and text()='example.com']",
]
}
)
assert_lib_ast('link: with multiple paragraphs',
'\\a[http://example.com][aaa\n\nbbb]\n',
[
a('P', [
a(
'a',
[
a('P', [t('aaa')]),
a('P', [t('bbb')]),
],
{'href': [t('http://example.com')]},
),
]),
]
)
assert_lib_ast('xss: content and href',
'\\a[ab&\\<>"\'cd][ef&\\<>"\'gh]{external}\n',
undefined,
{
assert_xpath_stdout: [
`//x:a[@href=concat('ab&<>"', "'", 'cd') and text()=concat('ef&<>"', "'", 'gh')]`,
]
}
)
assert_lib_error(
// {check} local file existence of \a and \Image and local link automodifications.
'link: relative reference to nonexistent file leads to failure in link',
`\\a[i-dont-exist]
`, 1, 3, 'README.bigb',
{
input_path_noext: 'README',
}
)
assert_lib_error(
'link: relative reference to nonexistent file leads to failure in image',
`\\Image[i-dont-exist]
`, 1, 7, 'README.bigb',
{
input_path_noext: 'README',
}
)
assert_lib_ast(
'link: relative reference to existent file does not lead to failure in link',
`\\a[i-exist]
`,
undefined,
{
input_path_noext: 'README',
filesystem: {
'i-exist': '',
}
}
)
assert_lib_ast(
'link: relative reference to existent file does not lead to failure in image',
`\\Image[i-exist]
`,
undefined,
{
input_path_noext: 'README',
filesystem: {
'i-exist': '',
}
}
)
assert_lib_ast(
'link: external prevents existence checks in link',
`\\a[i-dont-exist]{external}
`,
undefined,
{
input_path_noext: 'README',
}
)
assert_lib_ast(
'link: external prevents existence checks in block image',
`\\Image[i-dont-exist]{external}
`,
undefined,
{
input_path_noext: 'README',
}
)
assert_lib_ast(
'link: external prevents existence checks in inline image',
`\\image[i-dont-exist]{external}
`,
undefined,
{
input_path_noext: 'README',
}
)
assert_lib_ast(
'link: existence checks are skipped when media provider converts them to absolute url',
`\\Image[i-dont-exist]
`,
undefined,
{
input_path_noext: 'README',
convert_opts: {
ourbigbook_json: {
"media-providers": {
"github": {
"default-for": ["image"],
"remote": "cirosantilli/media"
},
}
},
}
}
)
assert_lib(
'link: relative links and images are corrected for different output paths with scope and split-headers',
{
convert_opts: {
split_headers: true,
},
convert_dir: true,
filesystem: {
'README.bigb': `= Index
== h2
{scope}
=== h3
\\a[i-exist][h3 i-exist]
\\Image[i-exist][h3 i-exist img]
\\Video[i-exist][h3 i-exist video]
\\a[subdir/i-exist-subdir][h3 i-exist-subdir]
\\a[https://cirosantilli.com][h3 abs]
\\a[file:///etc/fstab][h3 file:///etc/fstab]
`,
'subdir/README.bigb': `= Subdir
\\a[../i-exist][subdir i-exist]
\\a[/i-exist][subdir /i-exist]
\\a[/i-dont-exist][subdir /i-dont-exist external]{external}
\\a[i-exist-subdir][subdir i-exist-subdir]
== subdir h2
{scope}
=== subdir h3
\\a[../i-exist][subdir h3 i-exist]
\\a[/i-exist][subdir h3 /i-exist]
\\a[/i-dont-exist][subdir h3 /i-dont-exist external]{external}
\\a[i-exist-subdir][subdir h3 i-exist-subdir]
`,
'subdir/not-readme.bigb': `= Subdir Not Readme
\\a[../i-exist][subdir not readme i-exist]
\\a[i-exist-subdir][subdir not readme i-exist-subdir]
`,
'i-exist': ``,
'subdir/i-exist-subdir': ``,
},
assert_xpath: {
// TODO patch test system to make work.
//[`${ourbigbook.RAW_PREFIX}/i-exist`]: [],
//[`${ourbigbook.RAW_PREFIX}/subdir/i-exist`]: [],
'index.html': [
`//x:a[@href='${ourbigbook.RAW_PREFIX}/i-exist' and text()='h3 i-exist']`,
`//x:img[@src='${ourbigbook.RAW_PREFIX}/i-exist' and @alt='h3 i-exist img']`,
`//x:video[@src='${ourbigbook.RAW_PREFIX}/i-exist' and @alt='h3 i-exist video']`,
`//x:a[@href='${ourbigbook.RAW_PREFIX}/subdir/i-exist-subdir' and text()='h3 i-exist-subdir']`,
`//x:a[@href='https://cirosantilli.com' and text()='h3 abs']`,
],
'h2/h3.html': [
`//x:a[@href='../${ourbigbook.RAW_PREFIX}/i-exist' and text()='h3 i-exist']`,
`//x:a[@href='file:///etc/fstab' and text()='h3 file:///etc/fstab']`,
`//x:img[@src='../${ourbigbook.RAW_PREFIX}/i-exist' and @alt='h3 i-exist img']`,
`//x:video[@src='../${ourbigbook.RAW_PREFIX}/i-exist' and @alt='h3 i-exist video']`,
`//x:a[@href='https://cirosantilli.com' and text()='h3 abs']`,
],
'subdir.html': [
`//x:a[@href='${ourbigbook.RAW_PREFIX}/i-exist' and text()='subdir i-exist']`,
`//x:a[@href='${ourbigbook.RAW_PREFIX}/i-exist' and text()='subdir /i-exist']`,
`//x:a[@href='/i-dont-exist' and text()='subdir /i-dont-exist external']`,
`//x:a[@href='${ourbigbook.RAW_PREFIX}/subdir/i-exist-subdir' and text()='subdir i-exist-subdir']`,
],
'subdir/subdir-h2/subdir-h3.html': [
`//x:a[@href='../../${ourbigbook.RAW_PREFIX}/i-exist' and text()='subdir h3 i-exist']`,
`//x:a[@href='../../${ourbigbook.RAW_PREFIX}/i-exist' and text()='subdir h3 /i-exist']`,
`//x:a[@href='/i-dont-exist' and text()='subdir h3 /i-dont-exist external']`,
`//x:a[@href='../../${ourbigbook.RAW_PREFIX}/subdir/i-exist-subdir' and text()='subdir h3 i-exist-subdir']`,
],
'subdir/not-readme.html': [
`//x:a[@href='../${ourbigbook.RAW_PREFIX}/i-exist' and text()='subdir not readme i-exist']`,
`//x:a[@href='../${ourbigbook.RAW_PREFIX}/subdir/i-exist-subdir' and text()='subdir not readme i-exist-subdir']`,
],
},
}
)
// Internal cross references
// \x
assert_lib_ast('x: cross reference simple',
`= My header
\\x[my-header][link body]
`,
[
a('H', undefined, {
level: [t('1')],
title: [t('My header')],
}),
a('P', [
a('x', undefined, {
content: [t('link body')],
href: [t('my-header')],
}),
]),
],
)
assert_lib_ast('x: cross reference full boolean style without value',
`= My header
\\x[my-header]{full}
`,
[
a('H', undefined, {
level: [t('1')],
title: [t('My header')],
}),
a('P', [
a('x', undefined, {
full: [],
href: [t('my-header')],
}),
]),
]
)
assert_lib_ast('x: cross reference full boolean style with value 0',
`= abc
\\x[abc]{full=0}
`,
[
a('H', undefined, {
level: [t('1')],
title: [t('abc')],
}),
a('P', [
a('x', undefined, {
full: [t('0')],
href: [t('abc')],
}),
]),
]
)
assert_lib_ast('x: cross reference full boolean style with value 1',
`= abc
\\x[abc]{full=1}
`,
[
a('H', undefined, {
level: [t('1')],
title: [t('abc')],
}),
a('P', [
a('x', undefined, {
full: [t('1')],
href: [t('abc')],
}),
]),
]
)
assert_lib_error('x: cross reference full boolean style with invalid value 2',
`= abc
\\x[abc]{full=2}
`, 3, 8);
assert_lib_error('x: cross reference full boolean style with invalid value true',
`= abc
\\x[abc]{full=true}
`, 3, 8);
assert_lib_stdin('x: cross reference to image',
`\\Image[ab]{id=cd}{title=ef}
\\x[cd]
`, { filesystem: { ab: '' } });
assert_lib_stdin('x: cross reference without content nor target title style full',
`\\Image[ab]{id=cd}
\\x[cd]
`, { filesystem: { ab: '' } });
assert_lib_error('x: cross reference undefined fails gracefully', '\\x[ab]', 1, 3);
assert_lib_error('x: cross reference with child to undefined id fails gracefully',
`= h1
\\x[ab]{child}
`, 3, 3, undefined, {toplevel: true});
// https://docs.ourbigbook.com#order-of-reported-errors
assert_lib_error('x: cross reference undefined errors show after other errors',
`= a
\\x[b]
\`\`
== b
`, 5, 1);
assert_lib_error('x: cross reference full and ref are incompatible',
`= abc
\\x[abc]{full}{ref}
`, 3, 1);
assert_lib_error('x: cross reference content and full are incompatible',
`= abc
\\x[abc][def]{full}
`, 3, 1);
assert_lib_error('x: cross reference content and ref are incompatible',
`= abc
\\x[abc][def]{ref}
`, 3, 1);
assert_lib_error('x: cross reference full and c are incompatible',
`= abc
\\x[abc]{c}{full}
`, 3, 1);
assert_lib_error('x: cross reference full and p are incompatible',
`= abc
\\x[abc]{p}{full}
`, 3, 1);
assert_lib('x: cross reference to non-included toplevel header in another file',
{
convert_dir: true,
filesystem: {
'notindex.bigb': '\\x[another-file]',
'another-file.bigb': '= Another file',
},
assert_xpath: {
'notindex.html': [
"//x:a[@href='another-file.html' and text()='another file']",
]
},
},
)
assert_lib('x: cross reference to non-included non-toplevel header in another file',
{
convert_dir: true,
filesystem: {
'notindex.bigb': '\\x[another-file-2]',
'another-file.bigb': `= Another file
== Another file 2
`,
},
assert_xpath: {
'notindex.html': [
"//x:a[@href='another-file.html#another-file-2' and text()='another file 2']",
]
},
},
)
assert_lib('x: cross reference to included header in another file',
// I kid you not. Everything breaks everything.
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
\\x[another-file]
\\x[another-file-h2]
\\Include[another-file]
`,
'another-file.bigb': `= Another file
== Another file h2
`
},
assert_xpath: {
'notindex.html': [
"//x:a[@href='another-file.html' and text()='another file']",
"//x:a[@href='another-file.html#another-file-h2' and text()='another file h2']",
]
}
},
)
assert_lib_ast('x: cross reference to ids in the current file with split',
// TODO this test is ridiculously overbloated and is likely covered in other tests already.
`= Notindex
\\x[notindex]
\\x[bb]
\\Q[\\x[bb]{full}]
\\x[image-bb][image bb 1]
== bb
\\x[notindex][bb to notindex]
\\x[bb][bb to bb]
\\x[image-bb][bb to image bb]
\\Image[bb.png]{title=bb}
`,
[
a('H', undefined, {level: [t('1')], title: [t('Notindex')]}),
a('P', [a('x', undefined, {href: [t('notindex')]})]),
a('P', [a('x', undefined, {href: [t('bb')]})]),
a('Q', [a('x', undefined, {href: [t('bb')]})]),
a('P', [a('x', [t('image bb 1')], {href: [t('image-bb')]})]),
a('H', undefined, {level: [t('2')], title: [t('bb')]}),
a('P', [a('x', [t('bb to notindex')], {href: [t('notindex')]})]),
a('P', [a('x', [t('bb to bb')], {href: [t('bb')]})]),
a('P', [a('x', [t('bb to image bb')], {href: [t('image-bb')]})]),
a(
'Image',
undefined,
{
src: [t('bb.png')],
title: [t('bb')],
},
),
],
{
assert_xpath_stdout: [
// Empty URL points to start of the document, which is exactly what we want.
// https://stackoverflow.com/questions/5637969/is-an-empty-href-valid
"//x:div[@class='p']//x:a[@href='' and text()='notindex']",
"//x:a[@href='#bb' and text()='bb']",
"//x:blockquote//x:a[@href='#bb' and text()='Section 1. \"bb\"']",
// https://github.com/ourbigbook/ourbigbook/issues/94
"//x:a[@href='#bb' and text()='bb to bb']",
"//x:a[@href='#image-bb' and text()='image bb 1']",
// Links to the split versions.
xpath_header_split(1, 'notindex', 'notindex-split.html', ourbigbook.SPLIT_MARKER_TEXT),
xpath_header_split(2, 'bb', 'bb.html', ourbigbook.SPLIT_MARKER_TEXT),
],
assert_xpath: {
'notindex-split.html': [
"//x:a[@href='notindex.html#bb' and text()='bb']",
// https://github.com/ourbigbook/ourbigbook/issues/130
"//x:blockquote//x:a[@href='notindex.html#bb' and text()='Section 1. \"bb\"']",
// Link to the split version.
xpath_header_split(1, 'notindex', 'notindex.html', ourbigbook.NOSPLIT_MARKER_TEXT),
// Internal cross reference inside split header.
"//x:a[@href='notindex.html#image-bb' and text()='image bb 1']",
],
'bb.html': [
// Cross-page split-header parent link.
xpath_header_parent(1, 'bb', 'notindex.html', 'Notindex'),
"//x:a[@href='notindex.html' and text()='bb to notindex']",
"//x:a[@href='notindex.html#bb' and text()='bb to bb']",
// Link to the split version.
xpath_header_split(1, 'bb', 'notindex.html#bb', ourbigbook.NOSPLIT_MARKER_TEXT),
// Internal cross reference inside split header.
"//x:a[@href='#image-bb' and text()='bb to image bb']",
],
},
convert_opts: { split_headers: true },
filesystem: Object.assign({}, default_filesystem, {
'bb.png': ''
}),
input_path_noext: 'notindex',
},
)
assert_lib('x: splitDefault true and splitDefaultNotToplevel true',
{
convert_dir: true,
convert_opts: {
split_headers: true,
ourbigbook_json: {
h: {
splitDefault: true,
splitDefaultNotToplevel: true,
}
},
},
filesystem: {
'notindex.bigb': `= Notindex
\\x[index][notindex to index]
\\x[index-h2][notindex to index h2]
== Notindex h2
\\x[index][notindex h2 to index]
\\x[index-h2][notindex h2 to index h2]
=== Notindex h3
\\x[index][notindex h3 to index]
`,
'index.bigb': `= Index
\\x[index][index to index]
\\x[index-h2][index to index h2]
== Index h2
\\x[index][index h2 to index]
\\x[index-h2][index h2 to index h2]
=== Index h3
\\x[index][index h3 to index]
== Index h2 2
\\x[index-h2][index h2 2 to index h2]
`,
},
assert_xpath: {
'notindex.html': [
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex to index']",
"//x:div[@class='p']//x:a[@href='index-h2.html' and text()='notindex to index h2']",
// This output is not split.
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex h2 to index']",
"//x:div[@class='p']//x:a[@href='index-h2.html' and text()='notindex h2 to index h2']",
],
'index.html': [
"//x:div[@class='p']//x:a[@href='' and text()='index to index']",
"//x:div[@class='p']//x:a[@href='#index-h2' and text()='index to index h2']",
// This output is not split.
"//x:div[@class='p']//x:a[@href='' and text()='index h2 to index']",
"//x:div[@class='p']//x:a[@href='#index-h2' and text()='index h2 to index h2']",
// Links to the split versions.
xpath_header_split(2, 'index-h2', 'index-h2.html', ourbigbook.SPLIT_MARKER_TEXT),
],
'index-h2.html': [
"//x:div[@class='p']//x:a[@href='index.html' and text()='index h2 to index']",
"//x:div[@class='p']//x:a[@href='' and text()='index h2 to index h2']",
xpath_header_split(1, 'index-h2', 'index.html#index-h2', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'notindex-h2.html': [
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex h2 to index']",
"//x:div[@class='p']//x:a[@href='index-h2.html' and text()='notindex h2 to index h2']",
xpath_header_split(1, 'notindex-h2', 'notindex.html#notindex-h2', ourbigbook.NOSPLIT_MARKER_TEXT),
],
},
assert_not_xpath: {
'index.html': [
// There is no split version of this header.
xpath_header_split(1, 'index', undefined, ourbigbook.SPLIT_MARKER_TEXT),
],
'index-h2.html': [
// This output is split.
"//x:div[@class='p']//x:a[text()='index h3 to index']",
],
'notindex-h2.html': [
// This output is split.
"//x:div[@class='p']//x:a[text()='notindex h3 to index']",
],
},
assert_not_exists: [
'split.html',
'nosplit.html',
'notindex-split.html',
'notindex-nosplit.html',
],
},
)
assert_lib('x: splitDefault false and splitDefaultNotToplevel true',
{
convert_dir: true,
convert_opts: {
split_headers: true,
ourbigbook_json: {
h: {
splitDefault: false,
splitDefaultNotToplevel: true,
}
},
},
filesystem: {
'index.bigb': `= Index
\\x[index][index to index]
\\x[index-h2][index to index h2]
== Index h2
\\x[index][index h2 to index]
\\x[index-h2][index h2 to index h2]
=== Index h3
\\x[index][index h3 to index]
== Index h2 2
\\x[index-h2][index h2 2 to index h2]
`,
'notindex.bigb': `= Notindex
\\x[index][notindex to index]
\\x[index-h2][notindex to index h2]
== Notindex h2
\\x[index][notindex h2 to index]
\\x[index-h2][notindex h2 to index h2]
=== Notindex h3
\\x[index][notindex h3 to index]
`,
'no-children.bigb': `= No children
`,
},
assert_xpath: {
'notindex.html': [
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex to index']",
"//x:div[@class='p']//x:a[@href='index.html#index-h2' and text()='notindex to index h2']",
// This output is not split.
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex h2 to index']",
"//x:div[@class='p']//x:a[@href='index.html#index-h2' and text()='notindex h2 to index h2']",
],
'index.html': [
"//x:div[@class='p']//x:a[@href='' and text()='index to index']",
"//x:div[@class='p']//x:a[@href='#index-h2' and text()='index to index h2']",
// This output is not split.
"//x:div[@class='p']//x:a[@href='' and text()='index h2 to index']",
"//x:div[@class='p']//x:a[@href='#index-h2' and text()='index h2 to index h2']",
// Links to the split versions.
xpath_header_split(2, 'index-h2', 'index-h2.html', ourbigbook.SPLIT_MARKER_TEXT),
],
'index-h2.html': [
"//x:div[@class='p']//x:a[@href='index.html' and text()='index h2 to index']",
"//x:div[@class='p']//x:a[@href='index.html#index-h2' and text()='index h2 to index h2']",
// https://github.com/ourbigbook/ourbigbook/issues/271
xpath_header_split(1, 'index-h2', 'index.html#index-h2', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'notindex-h2.html': [
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex h2 to index']",
"//x:div[@class='p']//x:a[@href='index.html#index-h2' and text()='notindex h2 to index h2']",
// https://github.com/ourbigbook/ourbigbook/issues/271
xpath_header_split(1, 'notindex-h2', 'notindex.html#notindex-h2', ourbigbook.NOSPLIT_MARKER_TEXT),
],
},
assert_not_xpath: {
'index.html': [
// There is no split version of this header.
xpath_header_split(1, 'index', undefined, ourbigbook.SPLIT_MARKER_TEXT),
],
'no-children.html': [
// There is no split version of this header.
xpath_header_split(1, 'no-children', undefined, ourbigbook.SPLIT_MARKER_TEXT),
xpath_header_split(1, 'no-children', undefined, ourbigbook.NOSPLIT_MARKER_TEXT),
],
'index-h2.html': [
// This output is split.
"//x:div[@class='p']//x:a[text()='index h3 to index']",
],
'notindex-h2.html': [
// This output is split.
"//x:div[@class='p']//x:a[text()='notindex h3 to index']",
],
},
assert_not_exists: [
'split.html',
'nosplit.html',
'notindex-split.html',
'notindex-nosplit.html',
],
},
)
assert_lib(
'x: header splitDefault argument',
// https://github.com/ourbigbook/ourbigbook/issues/131
{
convert_dir: true,
convert_opts: { split_headers: true },
filesystem: {
'README.bigb': `= Toplevel
{splitDefault}
\\x[toplevel][toplevel to toplevel]
\\x[image-my-image-toplevel][toplevel to my image toplevel]
\\x[h2][toplevel to h2]
\\x[image-my-image-h2][toplevel to my image h2]
\\x[notindex][toplevel to notindex]
\\x[notindex-h2][toplevel to notindex h2]
\\Image[img.jpg]{title=My image toplevel}
== H2
\\x[toplevel][h2 to toplevel]
\\x[image-my-image-toplevel][h2 to my image toplevel]
\\x[h2][h2 to h2]
\\x[image-my-image-h2][h2 to my image h2]
\\x[notindex][h2 to notindex]
\\x[notindex-h2][h2 to notindex h2]
\\Image[img.jpg]{title=My image h2}
== Split suffix
{splitSuffix}
`,
'notindex.bigb': `= Notindex
\\x[toplevel][notindex to toplevel]
\\x[image-my-image-toplevel][notindex to my image toplevel]
\\x[h2][notindex to h2]
\\x[image-my-image-h2][notindex to my image h2]
\\x[notindex][notindex to notindex]
\\x[notindex-h2][notindex to notindex h2]
\\Image[img.jpg]{title=My image notindex}
== Notindex h2
\\x[toplevel][notindex h2 to toplevel]
\\x[image-my-image-toplevel][notindex h2 to my image toplevel]
\\x[h2][notindex h2 to h2]
\\x[image-my-image-h2][notindex h2 to my image h2]
\\x[notindex][notindex h2 to notindex]
\\x[notindex-h2][notindex h2 to notindex h2]
\\Image[img.jpg]{title=My image notindex h2}
`,
'img.jpg': '',
},
assert_xpath: {
// This is he split one.
'index.html': [
"//x:div[@class='p']//x:a[@href='' and text()='toplevel to toplevel']",
"//x:div[@class='p']//x:a[@href='h2.html' and text()='toplevel to h2']",
// That one is nosplit by default.
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='toplevel to notindex']",
// A child of a nosplit also becomes nosplit by default.
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='toplevel to notindex h2']",
// The toplevel split header does not get a numerical prefix.
xpath_header(1, 'toplevel', "x:a[@href='' and text()='Toplevel']"),
// Images.
"//x:div[@class='p']//x:a[@href='#image-my-image-toplevel' and text()='toplevel to my image toplevel']",
"//x:div[@class='p']//x:a[@href='h2.html#image-my-image-h2' and text()='toplevel to my image h2']",
// Split/nosplit.
xpath_header_split(1, 'toplevel', 'nosplit.html', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'nosplit.html': [
"//x:div[@class='p']//x:a[@href='' and text()='toplevel to toplevel']",
// Although h2 is split by defualt, it is already rendered in the curent page,
// so just link to the current page render instead.
"//x:div[@class='p']//x:a[@href='#h2' and text()='toplevel to h2']",
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='toplevel to notindex']",
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='toplevel to notindex h2']",
"//x:div[@class='p']//x:a[@href='' and text()='h2 to toplevel']",
"//x:div[@class='p']//x:a[@href='#h2' and text()='h2 to h2']",
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='h2 to notindex']",
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='h2 to notindex h2']",
// Images.
"//x:div[@class='p']//x:a[@href='#image-my-image-toplevel' and text()='toplevel to my image toplevel']",
"//x:div[@class='p']//x:a[@href='#image-my-image-h2' and text()='toplevel to my image h2']",
"//x:div[@class='p']//x:a[@href='#image-my-image-toplevel' and text()='h2 to my image toplevel']",
"//x:div[@class='p']//x:a[@href='#image-my-image-h2' and text()='h2 to my image h2']",
// Headers.
xpath_header(1, 'toplevel', "x:a[@href='index.html' and text()='Toplevel']"),
xpath_header(2, 'h2', "x:a[@href='h2.html' and text()='1. H2']"),
// Spilt/nosplit.
xpath_header_split(1, 'toplevel', 'index.html', ourbigbook.SPLIT_MARKER_TEXT),
],
'h2.html': [
"//x:div[@class='p']//x:a[@href='index.html' and text()='h2 to toplevel']",
"//x:div[@class='p']//x:a[@href='' and text()='h2 to h2']",
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='h2 to notindex']",
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='h2 to notindex h2']",
// The toplevel split header does not get a numerical prefix.
xpath_header(1, 'h2', "x:a[@href='' and text()='H2']"),
// Images.
"//x:div[@class='p']//x:a[@href='index.html#image-my-image-toplevel' and text()='h2 to my image toplevel']",
"//x:div[@class='p']//x:a[@href='#image-my-image-h2' and text()='h2 to my image h2']",
// Spilt/nosplit.
xpath_header_split(1, 'h2', 'nosplit.html#h2', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'split-suffix-split.html': [
],
'notindex.html': [
// Link so the split one of index because that's the default of that page.
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex to toplevel']",
"//x:div[@class='p']//x:a[@href='h2.html' and text()='notindex to h2']",
"//x:div[@class='p']//x:a[@href='' and text()='notindex to notindex']",
"//x:div[@class='p']//x:a[@href='#notindex-h2' and text()='notindex to notindex h2']",
// This is he nosplit one, so notindex h2 is also here.
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex h2 to toplevel']",
"//x:div[@class='p']//x:a[@href='h2.html' and text()='notindex h2 to h2']",
"//x:div[@class='p']//x:a[@href='' and text()='notindex h2 to notindex']",
"//x:div[@class='p']//x:a[@href='#notindex-h2' and text()='notindex h2 to notindex h2']",
// Images.
"//x:div[@class='p']//x:a[@href='h2.html#image-my-image-h2' and text()='notindex to my image h2']",
"//x:div[@class='p']//x:a[@href='h2.html#image-my-image-h2' and text()='notindex h2 to my image h2']",
// Headers.
xpath_header(1, 'notindex', "x:a[@href='notindex-split.html' and text()='Notindex']"),
xpath_header(2, 'notindex-h2', "x:a[@href='notindex-h2.html' and text()='1. Notindex h2']"),
// Spilt/nosplit.
xpath_header_split(1, 'notindex', 'notindex-split.html', ourbigbook.SPLIT_MARKER_TEXT),
],
'notindex-split.html': [
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex to toplevel']",
"//x:div[@class='p']//x:a[@href='h2.html' and text()='notindex to h2']",
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='notindex to notindex']",
// Link from split to another header inside the same nonsplit page.
// Although external links to this header would to to its default which is nosplit,
// mabe when we are inside it in split mode (a rarer use case) then we should just remain
// inside of split mode.
//"//x:div[@class='p']//x:a[@href='notindex-h2.html' and text()='notindex to notindex h2']",
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='notindex to notindex h2']",
// The toplevel split header does not get a numerical prefix.
xpath_header(1, 'notindex', "x:a[@href='' and text()='Notindex']"),
// Spilt/nosplit.
xpath_header_split(1, 'notindex', 'notindex.html', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'notindex-h2.html': [
"//x:div[@class='p']//x:a[@href='index.html' and text()='notindex h2 to toplevel']",
"//x:div[@class='p']//x:a[@href='h2.html' and text()='notindex h2 to h2']",
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='notindex h2 to notindex h2']",
// Link from split to another header inside the same nonsplit page.
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='notindex h2 to notindex']",
// The toplevel split header does not get a numerical prefix.
xpath_header(1, 'notindex-h2', "x:a[@href='' and text()='Notindex h2']"),
// Spilt/nosplit.
xpath_header_split(1, 'notindex-h2', 'notindex.html#notindex-h2', ourbigbook.NOSPLIT_MARKER_TEXT),
],
}
}
)
assert_lib('x: cross reference to non-included image in another file',
// https://github.com/ourbigbook/ourbigbook/issues/199
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
\\x[image-bb]
`,
'notindex2.bigb': `= Notindex2
== Notindex2 2
\\Image[aa]{external}
{title=bb}
`
},
assert_xpath: {
'notindex.html': [
"//x:div[@class='p']//x:a[@href='notindex2.html#image-bb' and text()='Figure \"bb\"']",
],
}
},
)
assert_lib_ast('x: cross reference with link inside it does not blow up',
`= asdf
{id=http://example.com}
\\x[http://example.com]
`,
[
a('H', undefined,
{
level: [t('1')],
title: [t('asdf')],
},
{
id: 'http:\/\/example.com',
}
),
a('P', [
a('x', undefined, {
href: [
a('a', undefined, {'href': [t('http:\/\/example.com')]}),
],
}),
]),
],
)
assert_lib('x: to image in another file that has x title in another file',
// https://github.com/ourbigbook/ourbigbook/issues/198
{
convert_dir: true,
filesystem: {
'tmp.bigb': `= Tmp
\\x[image-tmp2-2]
`,
'tmp2.bigb': `= Tmp2
\\Image[a]{external}
{title=\\x[tmp2-2]}
== Tmp2 2
`,
},
}
)
// TODO
//it('outputPathBase', () => {
// function assert(args, dirname, basename) {
// args.path_sep = '/'
// if (args.ast_undefined === undefined ) { args.ast_undefined = false }
// const ret = ourbigbook.outputPathBase(args)
// assert.strictEqual(ret.dirname, dirname)
// assert.strictEqual(ret.basename, basename)
// }
//
// assert({
// ast_undefined: false,
// ast_id,
// ast_input_path,
// ast_is_first_header_in_input_file,
// ast_split_default,
// ast_toplevel_id,
// context_to_split_headers,
// cur_toplevel_id,
// splitDefaultNotToplevel,
// split_suffix,
// }, )
//
// //assert(
// // {
// // 'notindex.bigb',
// // 'notindex',
// // },
// //);
// //assert.deepStrictEqual(
// // ourbigbook.output_path_parts(
// // 'index.bigb',
// // 'index',
// // context,
// // ),
// // ['', 'index']
// //);
// //assert.deepStrictEqual(
// // ourbigbook.output_path_parts(
// // 'README.bigb',
// // 'index',
// // context,
// // ),
// // ['', 'index']
// //);
//});
// Internal cross references \x
// https://github.com/ourbigbook/ourbigbook/issues/213
assert_lib_ast('x: cross reference magic simple sane',
`= Notindex
== My header
\\x[My headers]{magic}
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='#my-header' and text()='My headers']",
],
}
)
assert_lib_ast('x: cross reference magic simple insane',
`= Notindex
== My header
<My headers>
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='#my-header' and text()='My headers']",
],
}
)
assert_lib_ast('x: cross reference magic in title',
`= Notindex
== My header
\\Image[a.png]{external}
{title=<My headers> are amazing}
\\x[image-my-headers-are-amazing]
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='#image-my-headers-are-amazing' and text()='Figure 1. \"My headers are amazing\"']",
],
}
)
assert_lib_ast('x: cross reference magic insane escape',
`a\\<>b`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@class='p' and text()='a<>b']",
],
}
)
assert_lib_ast('x: cross reference magic with full uses full content',
`= Notindex
== My header
\\x[My headers]{magic}{full}
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='#my-header' and text()='Section 1. \"My header\"']",
],
}
)
assert_lib('x: cross reference magic cross file plural resolution',
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
<dogs>
<two dogs>
<my scope/in scope>
`,
'notindex2.bigb': `= Notindex2
== Dog
== Two dogs
== My Scope
{scope}
=== In scope
`,
},
assert_xpath: {
'notindex.html': [
"//x:div[@class='p']//x:a[@href='notindex2.html#dog' and text()='dogs']",
"//x:div[@class='p']//x:a[@href='notindex2.html#two-dogs' and text()='two dogs']",
"//x:div[@class='p']//x:a[@href='notindex2.html#my-scope/in-scope' and text()='in scope']",
],
},
},
)
assert_lib('x: cross reference magic detects capitalization and plural on output',
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
<Dog>
<two dogs>
<cat>
<one cats>
`,
'notindex2.bigb': `= Notindex2
== DoG
== Two Dogs
== Cat
{c}
== one Cat
`,
},
assert_xpath: {
'notindex.html': [
"//x:div[@class='p']//x:a[@href='notindex2.html#dog' and text()='DoG']",
"//x:div[@class='p']//x:a[@href='notindex2.html#two-dogs' and text()='two Dogs']",
"//x:div[@class='p']//x:a[@href='notindex2.html#cat' and text()='Cat']",
"//x:div[@class='p']//x:a[@href='notindex2.html#one-cat' and text()='one Cats']",
],
},
},
)
assert_lib_ast('x: cross reference magic insane to scope',
`= Notindex
\\Q[<My scope/In scope>]{id=same}
\\Q[<My scope/in scope>]{id=lower}
== My scope
{scope}
=== In scope
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@id='same']//x:blockquote//x:a[@href='#my-scope/in-scope' and text()='In scope']",
// Case is controlled only by the last scope component.
"//x:div[@id='lower']//x:blockquote//x:a[@href='#my-scope/in-scope' and text()='in scope']",
],
}
)
assert_lib_ast('x: cross reference magic insane to header file argument',
`= Notindex
<path/to/my_file.jpg>{file}
== path/to/my_file.jpg
{file}
`,
undefined,
{
assert_xpath_stdout: [
`//x:div[@class='p']//x:a[@href='#${ourbigbook.FILE_PREFIX}/path/to/my_file.jpg' and text()='path/to/my_file.jpg']`,
],
filesystem: {
'path/to/my_file.jpg': '',
},
}
)
assert_lib_ast('x: topic link: basic insane',
`a #Dogs b\n`,
[
a('P', [
t('a '),
a('x', undefined, {
href: [t('Dogs')],
topic: [],
}),
t(' b'),
]),
],
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='https://ourbigbook.com/go/topic/dog' and text()='Dogs']",
]
},
)
assert_lib_ast('x: topic link: at start of document does not blow up',
`#Dog\n`,
[
a('P', [
a('x', undefined, {
href: [t('Dog')],
topic: [],
}),
]),
],
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='https://ourbigbook.com/go/topic/dog' and text()='Dog']",
]
},
)
assert_lib_ast('x: topic link: insane escape',
'a \\#Dogs b\n',
[
a('P', [
t('a #Dogs b'),
]),
],
)
assert_lib('x: topic link: sane',
{
convert_dir: true,
filesystem: {
'README.bigb': `= tmp
\\x[Sane Link]{topic}
\\x[Sane Link With Content][My Content]{topic}
\\x[Many Dogs]{topic}
\\x[Many Cats]{topic}{p=1}
<#Insane Link>
`
},
assert_xpath: {
'index.html': [
"//x:div[@class='p']//x:a[@href='https://ourbigbook.com/go/topic/sane-link' and text()='Sane Link']",
"//x:div[@class='p']//x:a[@href='https://ourbigbook.com/go/topic/sane-link-with-content' and text()='My Content']",
"//x:div[@class='p']//x:a[@href='https://ourbigbook.com/go/topic/insane-link' and text()='Insane Link']",
"//x:div[@class='p']//x:a[@href='https://ourbigbook.com/go/topic/many-dog' and text()='Many Dogs']",
"//x:div[@class='p']//x:a[@href='https://ourbigbook.com/go/topic/many-cats' and text()='Many Cats']",
],
},
},
)
assert_lib_ast('x: cross reference c simple',
`= Tmp
== Dog
\\x[dog]{c}
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='#dog' and text()='Dog']",
],
}
)
assert_lib_ast('cross reference p simple',
`= Tmp
== Dog
\\x[dog]{p}
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='#dog' and text()='dogs']",
],
}
)
assert_lib_ast('x: cross reference c ignores non plaintext first argument',
// Maybe we shoud go deep into the first argument tree. But let's KISS for now.
`= Tmp
== \\i[Good] dog
\\x[good-dog]
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='#good-dog']//x:i[text()='Good']",
],
}
)
assert_lib_ast('x: cross reference p ignores non plaintext last argument',
// Maybe we shoud go deep into the last argument tree. But let's KISS for now.
`= Tmp
== Good \\i[dog]
\\x[good-dog]{p}
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='#good-dog']//x:i[text()='dog']",
],
}
)
assert_lib('x: x_external_prefix option',
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
\\x[notindex][notindex to notindex]
\\x[notindex-2][notindex to notindex 2]
\\x[notindex2][notindex to notindex2]
\\x[notindex2-2][notindex to notindex2 2]
== Notindex 2
`,
'notindex2.bigb': `= Notindex2
== Notindex2 2
`,
},
convert_opts: {
x_external_prefix: 'asdf/'
},
assert_xpath: {
'notindex.html': [
// Internal links: unchanged.
"//x:div[@class='p']//x:a[@href='' and text()='notindex to notindex']",
"//x:div[@class='p']//x:a[@href='#notindex-2' and text()='notindex to notindex 2']",
// External links: add the prefix.
"//x:div[@class='p']//x:a[@href='asdf/notindex2.html' and text()='notindex to notindex2']",
"//x:div[@class='p']//x:a[@href='asdf/notindex2.html#notindex2-2' and text()='notindex to notindex2 2']",
],
},
}
)
assert_lib('x: ourbigbook.json xPrefix',
{
convert_dir: true,
convert_opts: {
split_headers: true,
ourbigbook_json: {
xPrefix: 'asdf/',
},
},
filesystem: {
'index.bigb': `= Index
<Index>[index to index]
<Index 2>[index to index 2]
<Notindex>[index to notindex]
<Notindex 2>[index to notindex 2]
== Index 2
`,
'notindex.bigb': `= Notindex
== Notindex 2
`,
'subdir/notindex.bigb': `= Notindex
<Notindex 2>[notindex to notindex 2]
<Notindex2 2>[notindex to notindex2 2]
== Notindex 2
`,
'subdir/notindex2.bigb': `= Notindex2
== Notindex2 2
`,
'subdir/subdir2/notindex.bigb': `= Notindex
</Notindex 2>[subdir/subdir2/notindex to notindex 2]
`,
},
assert_xpath: {
'index.html': [
// Maybe we'd want:
//`//x:h1//x:a[@href='asdf']`,
// but would be slightly inconsistent with the following, so not sure...
"//x:div[@class='p']//x:a[@href='' and text()='index to index']",
"//x:div[@class='p']//x:a[@href='#index-2' and text()='index to index 2']",
"//x:div[@class='p']//x:a[@href='asdf/notindex.html' and text()='index to notindex']",
"//x:div[@class='p']//x:a[@href='asdf/notindex.html#notindex-2' and text()='index to notindex 2']",
],
'split.html': [
"//x:div[@class='p']//x:a[@href='asdf/index.html#index-2' and text()='index to index 2']",
"//x:div[@class='p']//x:a[@href='asdf/notindex.html#notindex-2' and text()='index to notindex 2']",
],
'notindex.html': [
// Don't add the suffix for -split or -nosplit outputs. Rationale: they don't
// exist on Web, which is the main use case that we redirect to. Just keep them working locally instead.
xpath_header_split(1, 'notindex', 'notindex-split.html', ourbigbook.SPLIT_MARKER_TEXT),
],
'subdir/notindex.html': [
"//x:div[@class='p']//x:a[@href='#notindex-2' and text()='notindex to notindex 2']",
"//x:div[@class='p']//x:a[@href='asdf/subdir/notindex2.html#notindex2-2' and text()='notindex to notindex2 2']",
],
'subdir/subdir2/notindex.html': [
"//x:div[@class='p']//x:a[@href='asdf/subdir/subdir2/../../notindex.html#notindex-2' and text()='subdir/subdir2/notindex to notindex 2']",
],
},
},
)
assert_lib(
'x: directory name is removed from link to subdir h2',
{
convert_dir: true,
filesystem: {
'README.bigb': `= Index
\\x[subdir/index-h2][link to subdir index h2]
`,
'ourbigbook.json': `{}\n`,
'subdir/index.bigb': `= Subdir index
== Index h2
`,
},
assert_xpath: {
'index.html': [
xpath_header(1, 'index'),
"//x:a[@href='subdir.html#index-h2' and text()='link to subdir index h2']",
]
},
}
)
// Infinite recursion.
// failing https://github.com/ourbigbook/ourbigbook/issues/34
assert_lib_error('cross reference from header title to following header is not allowed',
`= \\x[h2] aa
== h2
`, 1, 3);
assert_lib_error('cross reference from header title to previous header is not allowed',
`= h1
== \\x[h1] aa
`, 3, 4);
assert_lib_ast('cross reference from image title to previous non-header without content is not allowed',
`\\Image[ab]{title=cd}{external}
\\Image[ef]{title=gh \\x[image-cd]}{external}
`,
undefined,
{
input_path_noext: 'tmp',
invalid_title_titles: [
['image-gh-image-cd', 'tmp.bigb', 3, 1],
],
}
)
//// TODO https://docs.ourbigbook.com/todo/image-title-with-x-to-image-with-content-incorrectly-disallowed
//assert_lib(
// 'cross reference from image title to previous non-header with content is allowed',
// {
// convert_dir: true,
// filesystem: {
// 'README.bigb': `= Index
//
//\\Image[ab]{title=cd}{external}
//
//\\Image[ef]{title=gh \\x[image-cd][asdf]}{external}
//`,
// },
// assert_xpath: {
// 'index.html': [
// `//x:figure[@id='image-gh-asdf']`,
// ],
// },
// }
//);
//assert_lib(
// 'cross reference from image title to previous non-header with id is allowed',
// {
// convert_dir: true,
// filesystem: {
// 'README.bigb': `= Index
//
//\\Image[ab]{title=cd}{external}
//
//\\Image[ef]{title=gh \\x[image-cd]}{id=image-gh-asdf}{external}
//`,
// },
// assert_xpath: {
// 'index.html': [
// `//x:figure[@id='image-gh-asdf']`,
// ],
// },
// }
//);
assert_lib_ast('cross reference from image title to following non-header is not allowed',
`\\Image[ef]{title=gh \\x[image-cd]}{external}
\\Image[ab]{title=cd}{external}
`,
undefined,
{
input_path_noext: 'tmp',
invalid_title_titles: [
['image-gh-image-cd', 'tmp.bigb', 1, 1],
],
}
)
assert_lib_error('cross reference infinite recursion with explicit IDs fails gracefully',
`= \\x[h2]
{id=h1}
== \\x[h1]
{id=h2}
`, 1, 3);
assert_lib_error('cross reference infinite recursion to self IDs fails gracefully',
`= \\x[tmp]
`, 1, 3, 'tmp.bigb',
{
input_path_noext: 'tmp',
}
)
assert_lib_ast('cross reference from image to previous header with x content without image ID works',
`= ab
\\Image[cd]{title=\\x[ab][cd]}
`,
[
a('H', undefined, {
level: [t('1')],
title: [t('ab')],
}),
a(
'Image',
undefined,
{
src: [t('cd')],
title: [a('x', [t('cd')], {'href': [t('ab')]})],
},
),
],
{ filesystem: { cd: '' } },
)
assert_lib_ast('cross reference from image to previous header without x content with image ID works',
`= ab
\\Image[cd]{title=\\x[ab]}{id=cd}
`,
[
a('H', undefined, {
level: [t('1')],
title: [t('ab')],
}),
a('Image', undefined, {
id: [t('cd')],
src: [t('cd')],
title: [a('x', undefined, {'href': [t('ab')]})],
}),
],
{ filesystem: { cd: '' } },
)
assert_lib_ast('cross reference from image to previous header without x content without image ID works',
`= ab
\\Image[cd]{title=\\x[ab] cd}
`,
[
a('H', undefined, {
level: [t('1')],
title: [t('ab')],
}),
a(
'Image',
undefined,
{
src: [t('cd')],
title: [
a('x', undefined, {'href': [t('ab')]}),
t(' cd')
],
},
{
id: 'image-ab-cd',
}
),
],
{ filesystem: { cd: '' } },
)
assert_lib_ast('cross reference from image to following header without x content without image id works',
`= ab
\\Image[cd]{title=ef \\x[gh]}
== gh
`,
[
a('H', undefined, {
level: [t('1')],
title: [t('ab')],
}),
a(
'Image',
undefined,
{
src: [t('cd')],
title: [
t('ef '),
a('x', undefined, {'href': [t('gh')]})
],
},
{
id: 'image-ef-gh'
}
),
a('H', undefined, {
level: [t('2')],
title: [t('gh')],
}),
],
{ filesystem: { cd: '' } },
)
assert_lib_error('cross reference with parent to undefined ID does not throw',
`= aa
\\x[bb]{parent}
`,
3, 3
)
// Scope.
assert_lib_stdin("scope: internal cross references work with header scope and don't throw",
`= h1
\\x[h2-1/h3-1].
== h2 1
{scope}
\\x[h3-1]
=== h3 1
\\x[h3-2]
\\x[/h2-2]
\\x[h2-2]
==== h4 1
{scope}
===== h5 1
{scope}
=== h3 2
=== h2 2
== h2 2
`
)
assert_lib_ast('scope: with parent leading slash conflict resolution',
`= h1
= h2
{parent=h1}
= h3
{scope}
{parent=h2}
= h2
{parent=h3}
= h4
{parent=h2}
= h4
{parent=/h2}
`, [
a('H', undefined, {level: [t('1')], title: [t('h1')]}, {id: 'h1'}),
a('H', undefined, {level: [t('2')], title: [t('h2')]}, {id: 'h2'}),
a('H', undefined, {level: [t('3')], title: [t('h3')]}, {id: 'h3'}),
a('H', undefined, {level: [t('4')], title: [t('h2')]}, {id: 'h3/h2'}),
a('H', undefined, {level: [t('5')], title: [t('h4')]}, {id: 'h3/h4'}),
a('H', undefined, {level: [t('3')], title: [t('h4')]}, {id: 'h4'}),
]
)
assert_lib_ast('scope: with parent breakout with no leading slash',
`= h1
= h2
{parent=h1}
= h3
{scope}
{parent=h2}
= h4
{parent=h3}
= h5
{parent=h2}
`, [
a('H', undefined, {level: [t('1')], title: [t('h1')]}, {id: 'h1'}),
a('H', undefined, {level: [t('2')], title: [t('h2')]}, {id: 'h2'}),
a('H', undefined, {level: [t('3')], title: [t('h3')]}, {id: 'h3'}),
a('H', undefined, {level: [t('4')], title: [t('h4')]}, {id: 'h3/h4'}),
a('H', undefined, {level: [t('3')], title: [t('h5')]}, {id: 'h5'}),
]
)
// https://github.com/ourbigbook/ourbigbook/issues/120
assert_lib_ast('scope: nested with parent',
`= h1
{scope}
= h1 1
{parent=h1}
{scope}
= h1 1 1
{parent=h1-1}
= h1 1 2
{parent=h1-1}
= h1 1 3
{parent=h1/h1-1}
= h1 2
{parent=h1}
{scope}
= h1 2 1
{parent=h1-2}
{scope}
= h1 2 1 1
{parent=h1-2/h1-2-1}
`, [
a('H', undefined, {level: [t('1')], title: [t('h1')]}, {id: 'h1'}),
a('H', undefined, {level: [t('2')], title: [t('h1 1')]}, {id: 'h1/h1-1'}),
a('H', undefined, {level: [t('3')], title: [t('h1 1 1')]}, {id: 'h1/h1-1/h1-1-1'}),
a('H', undefined, {level: [t('3')], title: [t('h1 1 2')]}, {id: 'h1/h1-1/h1-1-2'}),
a('H', undefined, {level: [t('3')], title: [t('h1 1 3')]}, {id: 'h1/h1-1/h1-1-3'}),
a('H', undefined, {level: [t('2')], title: [t('h1 2')]}, {id: 'h1/h1-2'}),
a('H', undefined, {level: [t('3')], title: [t('h1 2 1')]}, {id: 'h1/h1-2/h1-2-1'}),
a('H', undefined, {level: [t('4')], title: [t('h1 2 1 1')]}, {id: 'h1/h1-2/h1-2-1/h1-2-1-1'}),
]
)
assert_lib_ast('scope: nested internal cross references resolves progressively',
`= h1
{scope}
= h1 1
{parent=h1}
{scope}
= h1 1 1
{parent=h1-1}
\\x[h1-1]
`, [
a('H', undefined, {level: [t('1')], title: [t('h1')]}, {id: 'h1'}),
a('H', undefined, {level: [t('2')], title: [t('h1 1')]}, {id: 'h1/h1-1'}),
a('H', undefined, {level: [t('3')], title: [t('h1 1 1')]}, {id: 'h1/h1-1/h1-1-1'}),
a('P', [a('x', undefined, {href: [t('h1-1')]})]),
]
)
// https://github.com/ourbigbook/ourbigbook/issues/100
assert_lib_error('scope: broken parent still generates a header ID',
`= h1
\\x[h2]
= h2
{parent=reserved-undefined}
`, 6, 1
)
assert_lib_ast('scope: cross reference to toplevel scoped split header',
`= Notindex
{scope}
== bb
\\x[cc][bb to cc]
\\x[image-bb][bb to image bb]
\\Image[bb.png]{title=bb}
== cc
\\x[image-bb][cc to image bb]
`,
[
a('H', undefined, {level: [t('1')], title: [t('Notindex')]}),
a('H', undefined, {level: [t('2')], title: [t('bb')]}),
a('P', [a('x', [t('bb to cc')], {href: [t('cc')]})]),
a('P', [a('x', [t('bb to image bb')], {href: [t('image-bb')]})]),
a(
'Image',
undefined,
{
src: [t('bb.png')],
title: [t('bb')],
},
),
a('H', undefined, {level: [t('2')], title: [t('cc')]}),
a('P', [a('x', [t('cc to image bb')], {href: [t('image-bb')]})]),
],
{
assert_xpath_stdout: [
// Not `#notindex/image-bb`.
// https://docs.ourbigbook.com#header-scope-argument-of-toplevel-headers
"//x:a[@href='#image-bb' and text()='bb to image bb']",
],
assert_xpath: {
'notindex/bb.html': [
"//x:a[@href='../notindex.html#cc' and text()='bb to cc']",
"//x:a[@href='#image-bb' and text()='bb to image bb']",
],
'notindex/cc.html': [
"//x:a[@href='../notindex.html#image-bb' and text()='cc to image bb']",
],
},
input_path_noext: 'notindex',
convert_opts: { split_headers: true },
filesystem: { 'bb.png': '' },
},
)
assert_lib_ast('scope: cross reference to non-toplevel scoped split header',
// https://github.com/ourbigbook/ourbigbook/issues/173
`= tmp
== tmp 2
{scope}
=== tmp 3
\\x[tmp][tmp 3 to tmp]
\\x[tmp-2][tmp 3 to tmp 2]
\\x[tmp-3][tmp 3 to tmp 3]
`,
[
a('H', undefined, {level: [t('1')], title: [t('tmp')]}),
a('H', undefined, {level: [t('2')], title: [t('tmp 2')]}),
a('H', undefined, {level: [t('3')], title: [t('tmp 3')]}),
a('P', [a('x', [t('tmp 3 to tmp')], {href: [t('tmp')]})]),
a('P', [a('x', [t('tmp 3 to tmp 2')], {href: [t('tmp-2')]})]),
a('P', [a('x', [t('tmp 3 to tmp 3')], {href: [t('tmp-3')]})]),
],
{
assert_xpath: {
'tmp-2/tmp-3.html': [
"//x:a[@href='../tmp.html' and text()='tmp 3 to tmp']",
"//x:a[@href='../tmp.html#tmp-2' and text()='tmp 3 to tmp 2']",
"//x:a[@href='../tmp.html#tmp-2/tmp-3' and text()='tmp 3 to tmp 3']",
],
},
convert_opts: { split_headers: true },
input_path_noext: 'tmp',
},
)
// https://docs.ourbigbook.com#header-scope-argument-of-toplevel-headers
assert_lib_ast('scope: cross reference to non-included file with toplevel scope',
`\\x[toplevel-scope]
\\x[toplevel-scope/h2]
\\x[toplevel-scope/image-h1][image h1]
\\x[toplevel-scope/image-h2][image h2]
`,
[
a('P', [a('x', undefined, {href: [t('toplevel-scope')]})]),
a('P', [a('x', undefined, {href: [t('toplevel-scope/h2')]})]),
a('P', [a('x', undefined, {href: [t('toplevel-scope/image-h1')]})]),
a('P', [a('x', undefined, {href: [t('toplevel-scope/image-h2')]})]),
],
{
assert_xpath_stdout: [
// Not `toplevel-scope.html#toplevel-scope`.
"//x:div[@class='p']//x:a[@href='toplevel-scope.html' and text()='toplevel scope']",
// Not `toplevel-scope.html#toplevel-scope/h2`.
"//x:div[@class='p']//x:a[@href='toplevel-scope.html#h2' and text()='h2']",
],
assert_xpath: {
// TODO https://github.com/ourbigbook/ourbigbook/issues/139
//'notindex-split.html': [
// "//x:a[@href='toplevel-scope.html#image-h1' and text()='image h1']",
// "//x:a[@href='toplevel-scope/h2.html#image-h2' and text()='image h2']",
//],
},
convert_before: ['toplevel-scope.bigb'],
input_path_noext: 'notindex',
convert_opts: { split_headers: true },
filesystem: {
'toplevel-scope.bigb': `= Toplevel scope
{scope}
\\Image[h1.png]{title=h1}
== h2
\\Image[h2.png]{title=h2}
`,
'h1.png': '',
'h2.png': '',
}
}
)
assert_lib_ast('scope: toplevel scope gets removed from IDs in the file',
`= Notindex
{scope}
\\x[notindex][link to notindex]
\\x[h2][link to h2]
== h2
`,
[
a('H', undefined, {level: [t('1')], title: [t('Notindex')]}),
a('P', [a('x', undefined, {href: [t('notindex')]})]),
a('P', [a('x', undefined, {href: [t('h2')]})]),
a('H', undefined, {level: [t('2')], title: [t('h2')]}),
],
{
assert_xpath_stdout: [
xpath_header(1, 'notindex'),
"//x:div[@class='p']//x:a[@href='' and text()='link to notindex']",
"//x:div[@class='p']//x:a[@href='#h2' and text()='link to h2']",
xpath_header(2, 'h2'),
],
}
)
assert_lib(
'incoming links: cross reference incoming links and other children simple',
{
convert_opts: {
split_headers: true,
},
convert_dir: true,
filesystem: {
'README.bigb': `= Index
\\x[index]
\\x[h2]
\\x[notindex]
\\x[h2-2]{child}
\\x[scope/scope-1]
== h2
{child=h2-3}
{child=h2-4}
{child=notindex-h2-2}
\\x[index]
\\x[notindex]
\\x[h2-2]{child}
\\x[scope/scope-2]{child}
== h2 2
== h2 3
== h2 4
== h2 5
{tag=h2}
== No incoming
== Scope 1
== Scope
{scope}
=== Scope 1
=== Scope 2
\\x[scope-1]
\\x[scope-3]{child}
=== Scope 3
== Dog
== Cats
`,
'notindex.bigb': `= Notindex
\\x[index]
\\x[h2]
== Notindex h2
{tag=h2-2}
=== Notindex h3
== Notindex h2 2
`,
},
assert_xpath: {
'index.html': [
// Would like to test like this, but it doesn't seem implemented in this crappy xpath implementation.
// So we revert to instrumentation instead then.
//`//x:h2[@id='_incoming-links']/following:://x:a[@href='#h2']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='#h2']`,
// https://github.com/ourbigbook/ourbigbook/issues/155
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='#h2-2']`,
],
'h2.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='index.html']`,
// https://github.com/ourbigbook/ourbigbook/issues/155
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='index.html#h2-2']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='index.html#h2-3']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='index.html#h2-4']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='index.html#h2-5']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='index.html#scope/scope-2']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='notindex.html#notindex-h2-2']`,
],
'h2-2.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='index.html']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='index.html#h2']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='notindex.html#notindex-h2']`,
],
'scope/scope-1.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='../index.html']`,
// https://github.com/ourbigbook/ourbigbook/issues/173
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='../index.html#scope/scope-2']`,
],
'scope/scope-2.html': [
// https://github.com/ourbigbook/ourbigbook/issues/173
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='../index.html#scope/scope-3']`,
],
'notindex.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='index.html']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='index.html#h2']`,
],
},
assert_not_xpath: {
'no-incoming.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']`,
],
'scope-1.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='index.html#scope/scope-2']`,
],
},
}
)
assert_lib(
'incoming links: cross reference incoming links from other file min notindex to index',
{
convert_opts: {
split_headers: true,
},
convert_dir: true,
filesystem: {
'README.bigb': `= Index
`,
'notindex.bigb': `= Notindex
\\x[index]
`,
},
assert_xpath: {
'index.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html']`,
],
},
}
)
assert_lib(
'incoming links: cross reference incoming links from other file min index to notindex',
{
convert_opts: {
split_headers: true,
},
convert_dir: true,
filesystem: {
'README.bigb': `= Index
\\x[notindex]
`,
'notindex.bigb': `= Notindex
`,
},
assert_xpath: {
'notindex.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='index.html']`,
],
},
}
)
assert_lib(
// We can have confusion between singular and plural here unless proper resolution is done.
'incoming links: cross reference incoming links and other children with magic',
{
convert_opts: {
split_headers: true,
},
convert_dir: true,
filesystem: {
'README.bigb': `= Index
== Dog
== Dogs
`,
'notindex.bigb': `= Notindex
== To dog
<dog>
== To dogs
<dogs>
`,
},
assert_xpath: {
'dog.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html#to-dog']`,
],
'dogs.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html#to-dogs']`,
],
},
assert_not_xpath: {
'dog.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html#to-dogs']`,
],
'dogs.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html#to-dog']`,
],
},
}
)
assert_lib(
'incoming links: from another source file to split header simple',
{
convert_dir: true,
convert_opts: { split_headers: true },
filesystem: {
'README.bigb': `= Index
== Dog
`,
'notindex.bigb': `= Notindex
== To dog
<dog>
`,
},
assert_xpath: {
'dog.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html#to-dog']`,
],
},
}
)
assert_lib(
'incoming links: from subdir without direct link to it resolves correctly',
// Hit a bug where the incoming link was resolving wrongly to subdir/notindex.html#subdir/to-dog
// because the File was not being fetched from DB. Adding an explicit link from "Dog" to "To dog"
// would then fix it as it fetched the File.
{
convert_dir: true,
convert_opts: { split_headers: true },
filesystem: {
'README.bigb': `= Index
== Dog
`,
'subdir/notindex.bigb': `= Notindex
== To dog
<dog>
`,
},
assert_xpath: {
'dog.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='subdir/notindex.html#to-dog']`,
],
},
}
)
assert_lib('x leading slash to escape scopes works across files',
{
convert_dir: true,
filesystem: {
'README.bigb': `\\x[/notindex]`,
'notindex.bigb': `= Notindex
`,
},
}
)
// TODO This test can only work after:
// https://github.com/ourbigbook/ourbigbook/issues/188
// There is no other way to test this currently, as we can't have scopes
// across source files, and since scope is a boolean, and therefore can only
// match the header's ID itself. The functionality has in theory been implemented
// in the commit that adds this commented out test.
//assert_lib('scopes hierarchy resolution works across files',
// {
// convert_dir: true,
// filesystem: {
// 'README.bigb': `= Index
//
//== Index scope
//{scope}
//
//\\Include[notindex]
//
//== Index scope 2
//{scope}
//
//\\x[notindex-h2][index scope 2 to notindex h2]`,
// 'notindex.bigb': `= Notindex
//
//== Notindex h2
//`,
// },
// assert_xpath: {
// 'index.html': [
// "//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='index scope 2 to notindex h2']",
// ]
// }
// }
//);
assert_lib('scope: hierarchy resolution works across files with directories and not magic',
// https://github.com/ourbigbook/ourbigbook/issues/229
{
convert_dir: true,
convert_opts: {
split_headers: true,
},
filesystem: {
'subdir/notindex.bigb': `= Notindex
\\x[notindex2][index to notindex2]
\\x[notindex2-h2][index to notindex2 h2]
== Notindex h2
{tag=notindex2}
{tag=notindex2-h2}
`,
'subdir/notindex2.bigb': `= Notindex2
== Notindex2 h2
`,
'subdir/subdir/notindex.bigb': `= Notindex
\\x[notindex-h2][subdir/subdir/notindex to subdir/notindex-h2]
`,
},
assert_xpath: {
'subdir/notindex.html': [
"//x:div[@class='p']//x:a[@href='notindex2.html#notindex2-h2' and text()='index to notindex2 h2']",
"//x:div[@id='notindex-h2']//x:span[@class='tags']//x:a[@href='notindex2.html']",
"//x:div[@id='notindex-h2']//x:span[@class='tags']//x:a[@href='notindex2.html#notindex2-h2']",
],
'subdir/notindex2.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='notindex.html#notindex-h2']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html']`,
],
'subdir/notindex-h2.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='subdir/notindex.html']`,
],
'subdir/subdir/notindex.html': [
"//x:div[@class='p']//x:a[@href='../notindex.html#notindex-h2' and text()='subdir/subdir/notindex to subdir/notindex-h2']",
],
},
}
)
assert_lib('scope: hierarchy resolution works across files with directories and magic plural',
{
convert_dir: true,
convert_opts: {
split_headers: true,
},
filesystem: {
'subdir/notindex.bigb': `= Notindex
\\x[dogs]{magic}
`,
'subdir/notindex2.bigb': `= Notindex2
== Dog
`,
},
}
)
assert_lib('scope: link from non subdir scope to subdir scope works',
// https://github.com/ourbigbook/ourbigbook/issues/284
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
{scope}
<notindex2>[notindex to notindex2]
== Notindex 2
<notindex2>[notindex 2 to notindex2]
`,
'notindex/notindex2.bigb': `= Notindex2
`,
},
assert_xpath: {
'notindex.html': [
"//x:div[@class='p']//x:a[@href='notindex/notindex2.html' and text()='notindex to notindex2']",
"//x:div[@class='p']//x:a[@href='notindex/notindex2.html' and text()='notindex 2 to notindex2']",
]
},
}
)
assert_lib('x: ref_prefix gets appeneded to absolute targets',
{
convert_dir: true,
convert_opts: {
split_headers: true,
ref_prefix: 'subdir',
},
filesystem: {
'subdir/notindex.bigb': `= Notindex
== Scope
{scope}
=== Notindex2
\\x[/notindex2][scope/notindex2 to notindex2]
`,
'subdir/notindex2.bigb': `= Notindex2
`,
},
assert_xpath: {
'subdir/notindex.html': [
"//x:div[@class='p']//x:a[@href='notindex2.html' and text()='scope/notindex2 to notindex2']",
],
'subdir/notindex2.html': [
//`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html']`,
],
},
}
)
assert_lib(
'x: link to image in another file after link to the toplevel header of that file does not blow up',
{
convert_dir: true,
filesystem: {
'README.bigb': `= Toplevel
\\Image[img.jpg]{title=My image toplevel}
`,
'notindex.bigb': `= Notindex
\\x[toplevel]
\\x[image-my-image-toplevel]
`,
'img.jpg': '',
},
}
)
assert_lib('x: split renders by default links back to nosplit render of another header in the same file',
{
convert_dir: true,
filesystem: {
'README.bigb': `= tmp
<tmp 2>[index to tmp 2]
== tmp 2
`
},
assert_xpath: {
'split.html': [
"//x:div[@class='p']//x:a[@href='index.html#tmp-2' and text()='index to tmp 2']",
],
},
convert_opts: { split_headers: true },
},
)
assert_lib('x: redirect from cirosantilli.com to ourbigbook.com',
{
convert_dir: true,
filesystem: {
'README.bigb': `= tmp
<tmp 2>[tmp to tmp 2]
<tmp2>[tmp to tmp2]
<tmp2 2>[tmp to tmp2 2]
== tmp 2
`,
'tmp2.bigb': `= tmp2
== tmp2 2
`,
},
convert_opts: {
split_headers: true,
ourbigbook_json: {
toSplitHeaders: true,
xPrefix: 'https://ourbigbook.com/cirosantilli/',
htmlXExtension: false,
},
},
assert_xpath: {
'index.html': [
"//x:div[@class='p']//x:a[@href='#tmp-2' and text()='tmp to tmp 2']",
"//x:div[@class='p']//x:a[@href='https://ourbigbook.com/cirosantilli/tmp2' and text()='tmp to tmp2']",
"//x:div[@class='p']//x:a[@href='https://ourbigbook.com/cirosantilli/tmp2-2' and text()='tmp to tmp2 2']",
],
},
assert_not_xpath: {
'index.html': [
xpath_header_split(1, 'tmp', 'tmp.html', ourbigbook.SPLIT_MARKER_TEXT),
],
'split.html': [
xpath_header_split(1, 'tmp', 'index.html', ourbigbook.NOSPLIT_MARKER_TEXT),
],
},
},
)
// Subdir.
assert_lib('header: subdir argument basic',
// This was introduced to handle Web uploads without path: API parameter.
// But in the end for some reason we ended up sticking with the path parameter to start with.
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
\\x[asdf/qwer/notindex2][notindex to notindex2]
\\x[asdf/qwer/notindex2-2][notindex to notindex2 2]
`,
'notindex2.bigb': `= Notindex2
{subdir=asdf/qwer}
== Notindex2 2
`,
},
assert_xpath: {
'notindex.html': [
"//x:div[@class='p']//x:a[@href='notindex2.html' and text()='notindex to notindex2']",
"//x:div[@class='p']//x:a[@href='notindex2.html#notindex2-2' and text()='notindex to notindex2 2']",
]
},
}
)
// Headers.
// \H
assert_lib_ast('header: simple',
`\\H[1][My header]
\\H[2][My header 2]
\\H[3][My header 3]
\\H[4][My header 4]
`,
[
a('H', undefined, {level: [t('1')], title: [t('My header')]}),
a('H', undefined, {level: [t('2')], title: [t('My header 2')]}),
a('H', undefined, {level: [t('3')], title: [t('My header 3')]}),
a('H', undefined, {level: [t('4')], title: [t('My header 4')]}),
],
{
assert_xpath_stdout: [
// The toplevel header does not have any numerical prefix, e.g. "1. My header",
// it is just "My header".
xpath_header(1, 'notindex', "x:a[@href='notindex-split.html' and text()='My header']"),
xpath_header(2, 'my-header-2', "x:a[@href='my-header-2.html' and text()='1. My header 2']"),
],
assert_xpath: {
'my-header-2.html': [
// The toplevel split header does not get a numerical prefix.
xpath_header(1, 'my-header-2', "x:a[@href='' and text()='My header 2']"),
],
'my-header-3.html': [
// The toplevel split header does not get a numerical prefix.
xpath_header(1, 'my-header-3', "x:a[@href='' and text()='My header 3']"),
],
},
convert_opts: { split_headers: true },
input_path_noext: 'notindex',
},
)
assert_lib_ast('header: and implicit paragraphs',
`\\H[1][My header 1]
My paragraph 1.
\\H[2][My header 2]
My paragraph 2.
`,
[
a('H', undefined, {level: [t('1')], title: [t('My header 1')]}),
a('P', [t('My paragraph 1.')]),
a('H', undefined, {level: [t('2')], title: [t('My header 2')]}),
a('P', [t('My paragraph 2.')]),
]
)
const header_7_expect = [
a('H', undefined, {level: [t('1')], title: [t('1')]}),
a('H', undefined, {level: [t('2')], title: [t('2')]}),
a('H', undefined, {level: [t('3')], title: [t('3')]}),
a('H', undefined, {level: [t('4')], title: [t('4')]}),
a('H', undefined, {level: [t('5')], title: [t('5')]}),
a('H', undefined, {level: [t('6')], title: [t('6')]}),
a('H', undefined, {level: [t('7')], title: [t('7')]}),
];
assert_lib_ast('header: 7 sane',
`\\H[1][1]
\\H[2][2]
\\H[3][3]
\\H[4][4]
\\H[5][5]
\\H[6][6]
\\H[7][7]
`,
header_7_expect
)
assert_lib_ast('header: 7 insane',
// https://github.com/ourbigbook/ourbigbook/issues/32
`= 1
== 2
=== 3
==== 4
===== 5
====== 6
======= 7
`,
header_7_expect
)
assert_lib_ast('header: 7 parent',
`= 1
= 2
{parent=1}
= 3
{parent=2}
= 4
{parent=3}
= 5
{parent=4}
= 6
{parent=5}
= 7
{parent=6}
`,
header_7_expect
)
assert_lib_ast('header: parent does title to ID conversion',
`= 1
= a b%c \`d\`
{parent=1}
= 3
{parent=a b%c \`d\`}
`,
[
a('H', undefined, {level: [t('1')], title: [t('1')]}),
a(
'H',
undefined,
{
level: [t('2')],
title: [
t('a b%c '),
a('c', [t('d')]),
],
},
{
id: 'a-b-percent-c-d'
}
),
a('H', undefined, {level: [t('3')], title: [t('3')]}),
],
)
assert_lib_error('header: with parent argument must have level equal 1',
`= 1
== 2
{parent=1}
`,
3, 1
)
assert_lib_error('header: parent cannot be an older id of a level',
`= 1
== 2
== 2 2
= 3
{parent=2}
`,
8, 1
)
assert_lib_error('header: header inside parent',
`= 1
== 2
{parent=1
== 3
}
`,
3, 1
)
assert_lib_error('header: child argument to id that does not exist gives an error',
`= 1
{child=2}
{child=3}
== 2
`,
3, 1
)
assert_lib_error('header: tag argument to id that does not exist gives an error',
`= 1
{tag=2}
{tag=3}
== 2
`,
3, 1
)
assert_lib('header: tag and child argument does title to ID conversion',
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= 1
== a b%c \`d\`
{child=d e%f}
== 3
{tag=a b%c \`d\`}
== d e%f
`,
},
}
)
assert_lib_error('header: child and synonym arguments are incompatible',
// This almost worked, but "Other children" links were not showing.
// Either we support it fully, or it blows up clearly, this immediately
// confused me a bit on cirosantilli.com.
`= 1
= 1 2
{synonym}
{child=2}
== 2
`,
5, 1
)
assert_lib_error('header: tag and synonym arguments are incompatible',
`= 1
= 1 2
{synonym}
{tag=2}
== 2
`,
5, 1
)
assert_lib_error('header: synonym without preceeding header fails gracefully',
`asdf
= qwer
{synonym}
`,
4, 1
)
//// This would be the ideal behaviour, but I'm lazy now.
//// https://github.com/ourbigbook/ourbigbook/issues/200
//assert_lib_ast('full link to synonym renders the same as full link to the main header',
// `= 1
//
//\\Q[\\x[1-3]{full}]
//
//== 1 2
//
//= 1 3
//{synonym}
//`,
// undefined,
// {
// assert_xpath_stdout: [
// "//x:blockquote//x:a[@href='#1-2' and text()='Section 1. \"1 2\"']",
// ],
// }
//);
// This is not the ideal behaviour, the above test would be the ideal.
// But it will be good enough for now.
// https://github.com/ourbigbook/ourbigbook/issues/200
assert_lib_ast('header: title2 full link to synonym with title2 does not get dummy empty parenthesis',
`= 1
\\Q[\\x[1-3]{full}]
== 1 2
= 1 3
{synonym}
{title2}
`,
undefined,
{
assert_xpath_stdout: [
"//x:blockquote//x:a[@href='#1-2' and text()='Section 1. \"1 3\"']",
],
}
)
assert_lib_ast('header: title2 shows next to title',
`= Asdf
{title2=qwer}
{title2=zxcv}
`,
undefined,
{
assert_xpath_stdout: [
xpath_header(1, 'asdf', "x:a[@href='' and text()='Asdf (qwer, zxcv)']"),
],
}
)
assert_lib_error('header: title2 of synonym must be empty',
`= 1
= 1 2
{synonym}
{title2=asdf}
`,
// 5, 9 would be better, pointing to the start of asdf
5, 1
)
assert_lib_error('header: title2 of synonym cannot be given multiple times',
`= 1
= 1 2
{synonym}
{title2}
{title2}
`,
// 6, 1 would be better, pointing to second title2
5, 1
)
assert_lib('header: synonym basic',
// https://github.com/ourbigbook/ourbigbook/issues/114
{
convert_opts: {
split_headers: true,
},
convert_dir: true,
filesystem: {
'README.bigb': `= Index
== h2
= My h2 synonym
{c}
{synonym}
\\x[h2]
\\x[my-h2-synonym]
\\x[my-notindex-h2-synonym]
= h3 parent
{parent=h2}
`,
'notindex.bigb': `= Notindex
== Notindex h2
= My notindex h2 synonym
{synonym}
`,
},
assert_xpath: {
'index.html': [
"//x:div[@class='p']//x:a[@href='#h2' and text()='h2']",
"//x:div[@class='p']//x:a[@href='#h2' and text()='My h2 synonym']",
// Across files to test sqlite db.
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='my notindex h2 synonym']",
],
'h2.html': [
// It does not generate a split header for `My h2 synonym`.
"//x:div[@class='p']//x:a[@href='index.html#h2' and text()='h2']",
],
//// Redirect generated by synonym.
//'my-h2-synonym.html': [
// "//x:script[text()=\"location='index.html#h2'\"]",
//],
// Redirect generated by synonym.
//'my-notindex-h2-synonym.html': [
// "//x:script[text()=\"location='notindex.html#notindex-h2'\"]",
//],
}
}
)
assert_lib('header: synonym in splitDefault',
// https://github.com/ourbigbook/ourbigbook/issues/225
{
convert_opts: {
split_headers: true,
},
convert_dir: true,
filesystem: {
'README.bigb': `= Index
{splitDefault}
== h2
= My h2 synonym
{c}
{synonym}
== h2 2
\\x[my-h2-synonym][h2 2 to my h2 synonym]
`,
},
assert_xpath: {
'h2-2.html': [
"//x:div[@class='p']//x:a[@href='h2.html' and text()='h2 2 to my h2 synonym']",
],
}
}
)
assert_lib('header: link to synonym toplevel does not have fragment',
// https://docs.ourbigbook.com/todo/links-to-synonym-header-have-fragment
{
convert_dir: true,
filesystem: {
'README.bigb': `= Index
<notindex>
<notindex 2>
`,
'notindex.bigb': `= Notindex
= Notindex 2
{synonym}
`,
},
assert_xpath: {
'index.html': [
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='notindex']",
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='notindex 2']",
],
}
}
)
const header_id_new_line_expect =
[a('H', undefined, {level: [t('1')], title: [t('aa')], id: [t('bb')]})];
assert_lib_ast('header id new line sane',
'\\H[1][aa]\n{id=bb}',
header_id_new_line_expect,
)
assert_lib_ast('header id new line insane no trailing elment',
'= aa\n{id=bb}',
header_id_new_line_expect,
)
assert_lib_ast('header id new line insane trailing element',
'= aa \\c[bb]\n{id=cc}',
[a('H', undefined, {
level: [t('1')],
title: [
t('aa '),
a('c', [t('bb')]),
],
id: [t('cc')],
})],
)
assert_lib_error('header: level must be an integer', '\\H[a][b]\n', 1, 3);
assert_lib_error('header: non integer h2 header level does not throw',
`\\H[1][h1]
\\H[][h2 1]
\\H[2][h2 2]
\\H[][h2 3]
`, 3, 3);
assert_lib_error('header: non integer h1 header level does not throw',
`\\H[][h1]
`, 1, 3);
assert_lib_error('header: must be an integer empty', '\\H[][b]\n', 1, 3);
assert_lib_error('header: must not be zero', '\\H[0][b]\n', 1, 3);
assert_lib_error('header: skip level is an error', '\\H[1][a]\n\n\\H[3][b]\n', 3, 3);
const header_numbered_input = `= tmp
\\Q[
\\x[tmp]{full}
\\x[tmp-2]{full}
\\x[tmp-3]{full}
\\x[tmp-4]{full}
\\x[tmp-5]{full}
\\x[tmp-6]{full}
\\x[tmp-7]{full}
\\x[tmp-8]{full}
]
== tmp 2
=== tmp 3
{numbered=0}
==== tmp 4
===== tmp 5
====== tmp 6
{numbered}
======= tmp 7
======== tmp 8
== tmp 2 2
=== tmp 2 2 3
`
assert_lib('header: numbered argument',
{
convert_dir: true,
filesystem: {
'README.bigb': header_numbered_input,
},
assert_xpath: {
'index.html': [
"//x:blockquote//x:a[@href='#tmp-2' and text()='Section 1. \"tmp 2\"']",
"//x:blockquote//x:a[@href='#tmp-4' and text()='Section \"tmp 4\"']",
"//x:blockquote//x:a[@href='#tmp-8' and text()='Section 1.1. \"tmp 8\"']",
"//*[@id='_toc']//x:a[@href='#tmp-2' and text()='1. tmp 2']",
"//*[@id='_toc']//x:a[@href='#tmp-3' and text()='1.1. tmp 3']",
"//*[@id='_toc']//x:a[@href='#tmp-4' and text()='tmp 4']",
"//*[@id='_toc']//x:a[@href='#tmp-5' and text()='tmp 5']",
"//*[@id='_toc']//x:a[@href='#tmp-6' and text()='tmp 6']",
"//*[@id='_toc']//x:a[@href='#tmp-7' and text()='1. tmp 7']",
"//*[@id='_toc']//x:a[@href='#tmp-8' and text()='1.1. tmp 8']",
"//*[@id='_toc']//x:a[@href='#tmp-2-2' and text()='2. tmp 2 2']",
"//*[@id='_toc']//x:a[@href='#tmp-2-2-3' and text()='2.1. tmp 2 2 3']",
],
'tmp-6.html': [
"//*[@id='_toc']//x:a[@href='index.html#tmp-7' and text()='1. tmp 7']",
"//*[@id='_toc']//x:a[@href='index.html#tmp-8' and text()='1.1. tmp 8']",
],
'tmp-7.html': [
"//*[@id='_toc']//x:a[@href='index.html#tmp-8' and text()='1. tmp 8']",
],
},
convert_opts: { split_headers: true },
},
)
assert_lib('header: numbered ourbigbook.json',
{
convert_dir: true,
filesystem: {
'README.bigb': header_numbered_input,
},
assert_xpath: {
'index.html': [
"//x:blockquote//x:a[@href='#tmp-2' and text()='Section \"tmp 2\"']",
"//x:blockquote//x:a[@href='#tmp-4' and text()='Section \"tmp 4\"']",
"//x:blockquote//x:a[@href='#tmp-8' and text()='Section 1.1. \"tmp 8\"']",
"//*[@id='_toc']//x:a[@href='#tmp-2' and text()='tmp 2']",
"//*[@id='_toc']//x:a[@href='#tmp-3' and text()='tmp 3']",
"//*[@id='_toc']//x:a[@href='#tmp-4' and text()='tmp 4']",
"//*[@id='_toc']//x:a[@href='#tmp-5' and text()='tmp 5']",
"//*[@id='_toc']//x:a[@href='#tmp-6' and text()='tmp 6']",
"//*[@id='_toc']//x:a[@href='#tmp-7' and text()='1. tmp 7']",
"//*[@id='_toc']//x:a[@href='#tmp-8' and text()='1.1. tmp 8']",
"//*[@id='_toc']//x:a[@href='#tmp-2-2' and text()='tmp 2 2']",
"//*[@id='_toc']//x:a[@href='#tmp-2-2-3' and text()='tmp 2 2 3']",
],
'tmp-6.html': [
"//*[@id='_toc']//x:a[@href='index.html#tmp-7' and text()='1. tmp 7']",
"//*[@id='_toc']//x:a[@href='index.html#tmp-8' and text()='1.1. tmp 8']",
],
'tmp-7.html': [
"//*[@id='_toc']//x:a[@href='index.html#tmp-8' and text()='1. tmp 8']",
],
},
convert_opts: {
split_headers: true,
ourbigbook_json: { h: { numbered: false } }
}
},
)
assert_lib('header: splitDefault on ourbigbook.json',
{
convert_dir: true,
convert_opts: {
split_headers: true,
ourbigbook_json: { h: { splitDefault: true } }
},
filesystem: {
'README.bigb': `= Index
\\Include[notindex]
== h2
`,
'notindex.bigb': `= Notindex
== Notindex h2
`
},
assert_xpath: {
'index.html': [
"//*[@id='_toc']//x:a[@href='notindex.html' and text()='1. Notindex']",
"//*[@id='_toc']//x:a[@href='notindex-h2.html' and text()='1.1. Notindex h2']",
],
'notindex.html': [
"//*[@id='_toc']//x:a[@href='notindex-h2.html' and text()='1. Notindex h2']",
],
},
},
)
assert_lib('header: file argument works',
{
convert_dir: true,
convert_opts: {
split_headers: true,
},
filesystem: {
'README.bigb': `= Index
== path/to
{file}
My directory
== path/to/my-file.txt
{file}
My txt
== path/to/my-file.png
{file}
My png
== path/to/my-file.mp4
{file}
My mp4
== Path to YouTube
{file=https://www.youtube.com/watch?v=YeFzeNAHEhU}
My youtube
`,
'path/to/my-file.txt': `My Line 1
My Line 2
`,
'path/to/my-file.png': '',
'path/to/my-file.mp4': '',
},
assert_xpath: {
'index.html': [
`//x:a[@href='_dir/path/index.html' and text()='path' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/path/to__path']`,
`//x:a[@href='_dir/path/to/index.html' and text()='to' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/path/to__path/to']`,
"//x:div[@class='p' and text()='My directory']",
"//x:a[@href='_raw/path/to/my-file.txt' and text()='my-file.txt']",
"//x:div[@class='p' and text()='My txt']",
// Don't know how to include newlines in xPath!
"//x:code[starts-with(text(), 'My Line 1')]",
`//x:a[@href='_dir/path/index.html' and text()='path' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/path/to/my-file.txt__path']`,
`//x:a[@href='_dir/path/to/index.html' and text()='to' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/path/to/my-file.txt__path/to']`,
"//x:a[@href='_raw/path/to/my-file.png' and text()='my-file.png']",
"//x:img[@src='_raw/path/to/my-file.png']",
"//x:div[@class='p' and text()='My png']",
"//x:a[@href='_raw/path/to/my-file.mp4' and text()='my-file.mp4']",
"//x:video[@src='_raw/path/to/my-file.mp4']",
"//x:div[@class='p' and text()='My mp4']",
"//x:a[@href='https://www.youtube.com/watch?v=YeFzeNAHEhU' and text()='www.youtube.com/watch?v=YeFzeNAHEhU']",
"//x:iframe[@src='https://www.youtube.com/embed/YeFzeNAHEhU']",
"//x:div[@class='p' and text()='My youtube']",
]
},
},
)
assert_lib('header: file argument that is the last header adds the preview',
{
convert_dir: true,
filesystem: {
'README.bigb': `= h1
== path/to/my-file.png
{file}
`,
'path/to/my-file.png': '',
},
assert_xpath: {
'index.html': [
"//x:a[@href='_raw/path/to/my-file.png' and text()='my-file.png']",
"//x:img[@src='_raw/path/to/my-file.png']",
]
}
},
)
assert_lib('header: file argument ignores text files on nosplit if they are too large',
{
convert_dir: true,
convert_opts: {
split_headers: true,
},
filesystem: {
'README.bigb': `= Index
== small.txt
{file}
== big.txt
{file}
`,
'small.txt': 'aaaa',
'big.txt': 'b'.repeat(ourbigbook.FILE_PREVIEW_MAX_SIZE + 1),
},
assert_xpath: {
'index.html': [
`//x:pre//x:code[text()='aaaa']`,
],
[`${ourbigbook.FILE_PREFIX}/small.txt.html`]: [
`//x:pre//x:code[text()='aaaa']`,
],
[`${ourbigbook.FILE_PREFIX}/big.txt.html`]: [
// Always show on split headers however.
`//x:pre//x:code[starts-with(text(), 'bbbb')]`,
],
},
assert_not_xpath: {
'index.html': [
`//x:pre//x:code[starts-with(text(), 'bbbb')]`,
],
}
},
)
assert_lib('header: file argument in subdir',
{
convert_dir: true,
filesystem: {
'path/to/notreadme.bigb': `= Notreadme
<my-file.txt>{file}
== my-file.txt
{file}
`,
'path/to/my-file.txt': `My Line 1
My Line 2
`,
},
assert_xpath: {
[`path/to/notreadme.html`]: [
`//x:a[@href='#${ourbigbook.FILE_PREFIX}/my-file.txt' and text()='path/to/my-file.txt']`,
// Maybe ID should be full path for _file. It's 3AM and I don't have the brains for this kind of stuff now.
xpath_header(2, `${ourbigbook.FILE_PREFIX}/my-file.txt`, `x:a[@href='#${ourbigbook.FILE_PREFIX}/my-file.txt' and text()='1. path/to/my-file.txt']`),
],
}
},
)
assert_lib('header: file argument in _file directory toplevel header',
{
convert_dir: true,
filesystem: {
'README.bigb': `= Index
<path/to/my-file.txt>{file}
`,
[`${ourbigbook.FILE_PREFIX}/path/to/my-file.txt.bigb`]: `= my-file.txt
{file}
My txt.
`,
[`${ourbigbook.FILE_PREFIX}/path/to/my-file.png.bigb`]: `= my-file.png
{file}
My png txt.
`,
'path/to/my-file.txt': `My Line 1
My Line 2
`,
'path/to/my-file.png': `aaaa`,
},
assert_xpath: {
[`index.html`]: [
// TODO text() should show path/to/my-file.txt. Maybe this could likely be factored out with
// the existing header handling code that adds full path to h1.
`//x:a[@href='${ourbigbook.FILE_PREFIX}/path/to/my-file.txt.html' and text()='my-file.txt']`,
],
[`${ourbigbook.FILE_PREFIX}/path/to/my-file.txt.html`]: [
"//x:a[@href='../../../_raw/path/to/my-file.txt' and text()='my-file.txt']",
// We actually get the full path always on the title of a {file} header.
"//x:h1//x:a[text()='path/to/my-file.txt']",
"//x:div[@class='p' and text()='My txt.']",
// Don't know how to include newlines in xPath!
"//x:code[starts-with(text(), 'My Line 1')]",
`//x:a[@href='../../../${ourbigbook.DIR_PREFIX}/index.html' and text()='${ourbigbook.FILE_ROOT_PLACEHOLDER}']`,
`//x:a[@href='../../../${ourbigbook.DIR_PREFIX}/path/index.html' and text()='path' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/path/to/my-file.txt__path']`,
`//x:a[@href='../../../${ourbigbook.DIR_PREFIX}/path/to/index.html' and text()='to' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/path/to/my-file.txt__path/to']`,
],
[`${ourbigbook.FILE_PREFIX}/path/to/my-file.png.html`]: [
"//x:img[@src='../../../_raw/path/to/my-file.png']",
],
}
},
)
assert_lib_error('header: file argument to a toplevel file that does not exist fails gracefully',
`= h1
== dont-exist
{file}
`, 3, 1);
assert_lib_ast('header: escape insane header at start of document',
'\\= a',
[a('P', [t('= a')])],
)
assert_lib('header: toplevel argument',
{
convert_dir: true,
convert_opts: {
split_headers: true,
},
filesystem: {
'README.bigb': `= Index
<h 1>[index to h 1]
<h 1 1>[index to h 1 1]
<h 1 1 1>[index to h 1 1 1]
<image 1 1 1>[index to image 1 1 1]
<h 1 1 1 1>[index to h 1 1 1 1]
<h 1 1 1 1 1>[index to h 1 1 1 1 1]
<h 1 1 1 1 1 1>[index to h 1 1 1 1 1 1]
<h 2>[index to h 2]
<h 2/h 2 1>[index to h 2 1]
<h 2/h 2 1 1>[index to h 2 1 1]
<h 2/h 2 1 1 1>[index to h 2 1 1 1]
<notindex>[index to notindex]
<notindex 1>[index to notindex 1]
<notindex 1 1>[index to notindex 1 1]
<notindex 1 1 1>[index to notindex 1 1 1]
== h 1
=== h 1 1
{toplevel}
==== h 1 1 1
\\Image[asdf.png]{title=1 1 1}{external}
\\Include[notindex]
===== h 1 1 1 1
====== h 1 1 1 1 1
{toplevel}
======= h 1 1 1 1 1 1
======= h 1 1 1 1 1 2
====== h 1 1 1 1 2
==== h 1 1 2
== h 2
{scope}
=== h 2 1
==== h 2 1 1
{toplevel}
===== h 2 1 1 1
`,
'notindex.bigb': `= Notindex
== Notindex 1
=== Notindex 1 1
{toplevel}
==== Notindex 1 1 1
`
},
assert_xpath: {
'index.html': [
// Same as without toplevel sanity checks.
xpath_header(1, 'index'),
xpath_header(2, 'h-1'),
"//x:div[@class='p']//x:a[@href='#h-1' and text()='index to h 1']",
"//x:div[@class='p']//x:a[@href='#h-2' and text()='index to h 2']",
"//x:div[@class='p']//x:a[@href='#h-2/h-2-1' and text()='index to h 2 1']",
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='index to notindex']",
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-1' and text()='index to notindex 1']",
"//x:div[@class='p']//x:a[@href='notindex-1-1.html' and text()='index to notindex 1 1']",
"//x:div[@class='p']//x:a[@href='notindex-1-1.html#notindex-1-1-1' and text()='index to notindex 1 1 1']",
"//*[@id='_toc']//x:a[@href='#h-1' and text()='1. h 1']",
"//*[@id='_toc']//x:a[@href='h-1-1.html' and text()='1.1. h 1 1']",
"//*[@id='_toc']//x:a[@href='h-1-1.html#h-1-1-1' and text()='1.1.1. h 1 1 1']",
// Modified by toplevel.
"//x:div[@class='p']//x:a[@href='h-1-1.html' and text()='index to h 1 1']",
"//x:div[@class='p']//x:a[@href='h-1-1.html#h-1-1-1' and text()='index to h 1 1 1']",
"//x:div[@class='p']//x:a[@href='h-1-1.html#image-1-1-1' and text()='index to image 1 1 1']",
"//x:div[@class='p']//x:a[@href='h-1-1.html#h-1-1-1-1' and text()='index to h 1 1 1 1']",
"//x:div[@class='p']//x:a[@href='h-1-1-1-1-1.html' and text()='index to h 1 1 1 1 1']",
"//x:div[@class='p']//x:a[@href='h-1-1-1-1-1.html#h-1-1-1-1-1-1' and text()='index to h 1 1 1 1 1 1']",
"//x:div[@class='p']//x:a[@href='h-2/h-2-1-1.html' and text()='index to h 2 1 1']",
"//x:div[@class='p']//x:a[@href='h-2/h-2-1-1.html#h-2-1-1-1' and text()='index to h 2 1 1 1']",
//// How it would be without toplevel.
//xpath_header(3, 'h-1-1'),
//xpath_header(4, 'h-1-1-1'),
//xpath_header(5, 'h-1-1-1-1'),
//xpath_header(6, 'h-1-1-1-1-1'),
//xpath_header(7, 'h-1-1-1-1-1-1'),
//"//x:div[@class='p']//x:a[@href='#h-1-1' and text()='index to h 1 1']",
//"//x:div[@class='p']//x:a[@href='#h-1-1-1' and text()='index to h 1 1 1']",
//"//x:div[@class='p']//x:a[@href='#h-1-1-1-1' and text()='index to h 1 1 1 1']",
//"//x:div[@class='p']//x:a[@href='#h-1-1-1-1-1' and text()='index to h 1 1 1 1 1']",
//"//x:div[@class='p']//x:a[@href='#h-1-1-1-1-1-1' and text()='index to h 1 1 1 1 1 1']",
],
'h-1-1.html': [
xpath_header(1, 'h-1-1'),
xpath_header(2, 'h-1-1-1'),
xpath_header(3, 'h-1-1-1-1'),
],
'h-1-1-split.html': [
xpath_header(1, 'h-1-1'),
],
'h-1-1-1-1-1.html': [
xpath_header(1, 'h-1-1-1-1-1'),
xpath_header(2, 'h-1-1-1-1-1-1'),
],
'h-2/h-2-1-1.html': [
xpath_header(1, 'h-2-1-1'),
xpath_header(2, 'h-2-1-1-1'),
],
'h-2/h-2-1-1-split.html': [
xpath_header(1, 'h-2-1-1'),
],
'notindex.html': [
xpath_header(1, 'notindex'),
xpath_header(2, 'notindex-1'),
],
'notindex-1-1.html': [
xpath_header(1, 'notindex-1-1'),
xpath_header(2, 'notindex-1-1-1'),
],
'notindex-1-1-split.html': [
xpath_header(1, 'notindex-1-1'),
],
},
assert_not_xpath: {
'index.html': [
xpath_header(4, 'h-1-1-1'),
xpath_header(5, 'h-1-1-1-1'),
xpath_header(6, 'h-1-1-1-1-1'),
xpath_header(7, 'h-1-1-1-1-1-1'),
],
'h-1-1-split.html': [
xpath_header(2, 'h-1-1-1'),
xpath_header(3, 'h-1-1-1-1'),
],
'h-2/h-2-1-1-split.html': [
xpath_header(2, 'h-2-1-1-1'),
],
'notindex.html': [
xpath_header(3, 'notindex-1-1'),
xpath_header(4, 'notindex-1-1-1'),
],
'notindex-1-1-split.html': [
xpath_header(2, 'notindex-1-1-1'),
],
}
},
)
assert_lib_ast('header: id of first header comes from the file name if not index',
// https://docs.ourbigbook.com#the-id-of-the-first-header-is-derived-from-the-filename
`= abc
\\x[notindex]
`,
[
a('H', undefined,
{
level: [t('1')],
title: [t('abc')],
},
{
id: 'notindex',
}
),
a('P', [
a('x', undefined, {
full: [t('0')],
href: [t('notindex')],
}),
]),
],
{
input_path_noext: 'notindex'
},
)
assert_lib_ast('header: id of first header comes from header title if index',
`= abc
\\x[abc]
`,
[
a('H', undefined,
{
level: [t('1')],
title: [t('abc')],
},
{
id: 'abc',
}
),
a('P', [
a('x', undefined, {
full: [t('0')],
href: [t('abc')],
}),
]),
],
{
convert_opts: {
input_path: ourbigbook.INDEX_BASENAME_NOEXT + '.' + ourbigbook.OURBIGBOOK_EXT
}
},
)
assert_lib_error('header: empty include in header title fails gracefully',
// https://github.com/ourbigbook/ourbigbook/issues/195
`= tmp
== \\Include
`,
3, 4
)
assert_lib_error('header: empty x in header title fails gracefully',
`= tmp
== a \\x
`,
3, 6
)
assert_lib_error('header: inside header fails gracefully',
`= \\H[2]
`,
1, 3, 'tmp.bigb',
{
input_path_noext: 'tmp',
}
)
assert_lib_error('header: forbid_multiheader option forbids multiple headers',
`= h1
== h2
`,
3, 1, 'tmp.bigb',
{
convert_opts: {
forbid_multiheader: 'denied',
},
input_path_noext: 'tmp',
}
)
assert_lib_stdin('header: forbid_multiheader option allows synonyms',
`= h1
= h2
{synonym}
`,
{
convert_opts: {
forbid_multiheader: 'denied',
},
}
)
assert_lib_error('header: forbid_multi_h1 option forbids multiple h1 headers',
`= h1
= h1 1
`,
3, 1, 'tmp.bigb',
{
convert_opts: { forbid_multi_h1: true },
input_path_noext: 'tmp',
}
)
assert_lib('header: forbid_multi_h1 option does not forbid multiple non-h1 headers',
{
convert_opts: { forbid_multi_h1: true },
convert_dir: true,
filesystem: {
'README.bigb': `= h1
== h2
== h2 2
=== h3
`,
},
}
)
assert_lib('header: forbid_multi_h1 option does not forbid synonym headers',
{
convert_opts: { forbid_multi_h1: true },
convert_dir: true,
filesystem: {
'README.bigb': `= h1
= h1 2
{synonym}
`,
},
}
)
assert_lib('header: forbid_multi_h1 option does not forbid h1 headers with parent',
{
convert_opts: { forbid_multi_h1: true },
convert_dir: true,
filesystem: {
'README.bigb': `= h1
= h2
{parent=h1}
`,
},
}
)
assert_lib_stdin('header: wiki argument without value adds a link to wikipedia based on the title',
`= My topic
{wiki}
`,
{
assert_xpath_stdout: [
"//x:a[@href='https://en.wikipedia.org/wiki/My_topic']",
]
}
)
assert_lib_stdin('header: wiki argument with a value adds a link to wikipedia with that value',
`= My topic
{wiki=Another_one}
`,
{
assert_xpath_stdout: [
"//x:a[@href='https://en.wikipedia.org/wiki/Another_one']",
]
}
)
// Code.
assert_lib_ast('code: inline sane',
'a \\c[b c] d\n',
[
a('P', [
t('a '),
a('c', [t('b c')]),
t(' d'),
]),
],
)
assert_lib_ast('code: inline insane simple',
'a `b c` d\n',
[
a('P', [
t('a '),
a('c', [t('b c')]),
t(' d'),
]),
]
)
// https://github.com/ourbigbook/ourbigbook/issues/171
assert_lib_ast('code: inline insane with only a backslash',
'a `\\` d\n',
[
a('P', [
t('a '),
a('c', [t('\\')]),
t(' d'),
]),
]
)
assert_lib_ast('code: inline insane escape backtick',
'a \\`b c\n',
[a('P', [t('a `b c')])]
)
assert_lib_ast('code: block literal sane',
`a
\\C[[
b
c
]]
d
`,
[
a('P', [t('a')]),
a('C', [t('b\nc')]),
a('P', [t('d')]),
]
)
assert_lib_ast('code: block insane',
`a
\`\`
b
c
\`\`
d
`,
[
a('P', [t('a')]),
a('C', [t('b\nc')]),
a('P', [t('d')]),
]
)
assert_lib_ast('code: with id has caption',
`\`\`
aa
\`\`
{id=bb}
`,
[
a('C', [t('aa')], { id: [t('bb')] }, { id: 'bb'} ),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Code 1']",
]
}
)
assert_lib_ast('code: with title has caption',
`\`\`
aa
\`\`
{title=b b}
`,
[
a('C', [t('aa')], { title: [t('b b')] }, { id: 'code-b-b'} ),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Code 1']",
]
}
)
assert_lib_ast('code: with description has caption',
`\`\`
aa
\`\`
{description=b b}
`,
[
a('C', [t('aa')], { description: [t('b b')] }, { id: '_1'} ),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Code 1']",
]
}
)
assert_lib_ast('code: without id, title, nor description does not have caption',
`\`\`
aa
\`\`
`,
[
a('C', [t('aa')], {}, { id: '_1'} ),
],
{
assert_not_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Code 1']",
]
}
)
assert_lib_ast('code: without id, title, nor description does not increment the code count',
`\`\`
aa
\`\`
{id=00}
\`\`
bb
\`\`
\`\`
cc
\`\`
{id=22}
`,
[
a('C', [t('aa')], { id: [t('00')] }, { id: '00'} ),
a('C', [t('bb')], {}, { id: '_1'} ),
a('C', [t('cc')], { id: [t('22')] }, { id: '22'} ),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Code 1']",
"//x:span[@class='caption-prefix' and text()='Code 2']",
],
assert_not_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Code 3']",
],
},
)
// lint h-parent
assert_lib_stdin('header parent works with ourbigbook.json lint h-parent equal parent and no includes',
`= 1
= 2
{parent=1}
`,
{ convert_opts: { ourbigbook_json: { lint: { 'h-parent': 'parent', } } } }
)
assert_lib_error('header number fails with ourbigbook.json lint h-parent = parent',
`= 1
== 2
`,
3, 1, undefined,
{ convert_opts: { ourbigbook_json: { lint: { 'h-parent': 'parent', } } } }
)
assert_lib_stdin('header number works with ourbigbook.json lint h-parent = number',
`= 1
== 2
`,
{ convert_opts: { ourbigbook_json: { lint: { 'h-parent': 'number', } } } }
)
assert_lib_error('header parent fails with ourbigbook.json lint h-parent = number',
`= 1
= 2
{parent=1}
`,
3, 1, undefined,
{ convert_opts: { ourbigbook_json: { lint: { 'h-parent': 'number', } } } }
)
assert_lib_stdin('header parent works with ourbigbook.json lint h-parent equal parent and includes with parent',
`= 1
= 2
{parent=1}
\\Include[include-two-levels-parent]
`,
{
convert_opts: {
ourbigbook_json: { lint: { 'h-parent': 'parent', } },
embed_includes: true,
}
}
)
assert_lib_error('header parent fails with ourbigbook.json lint h-parent equal parent and includes with number',
`= 1
= 2
{parent=1}
\\Include[include-two-levels]
`,
5, 1, 'include-two-levels.bigb',
{
convert_opts: {
ourbigbook_json: { lint: { 'h-parent': 'parent', } },
embed_includes: true,
}
}
)
// lint h-tag
assert_lib_error('lint h-tag child failure',
`= 1
{tag=2}
== 2
`,
2, 1, undefined,
{ convert_opts: { ourbigbook_json: { lint: { 'h-tag': 'child', } } } }
)
assert_lib_stdin('lint h-tag child pass',
`= 1
{child=2}
== 2
`,
{ convert_opts: { ourbigbook_json: { lint: { 'h-tag': 'child', } } } }
)
assert_lib_error('lint h-tag tag failure',
`= 1
{child=2}
== 2
`,
2, 1, undefined,
{ convert_opts: { ourbigbook_json: { lint: { 'h-tag': 'tag', } } } }
)
assert_lib_stdin('lint h-tag tag pass',
`= 1
{tag=2}
== 2
`,
{ convert_opts: { ourbigbook_json: { lint: { 'h-tag': 'tag', } } } }
)
// Word counts.
assert_lib_ast('word count simple',
`= h1
11 22 33
`,
undefined,
{
assert_xpath_stdout: [
"//*[contains(@class, 'h-nav')]//*[@class='word-count' and text()='3']",
],
}
)
assert_lib_ast('word count x',
`= h1
I like \\x[my-h2]
== My h2
`,
undefined,
{
assert_xpath_stdout: [
// TODO the desired value is 4. 2 is not terrible though, better than 3 if we were considering the href.
"//*[contains(@class, 'h-nav')]//*[@class='word-count' and text()='2']",
],
}
)
assert_lib_ast('word count descendant in source',
`= h1
11 22 33
== h2
44 55
`,
undefined,
{
assert_xpath_stdout: [
"//*[contains(@class, 'h-nav')]//*[@class='word-count' and text()='3']",
"//*[contains(@class, 'h-nav')]//*[@class='word-count-descendant' and text()='5']",
],
assert_xpath: {
'h2.html': [
"//*[contains(@class, 'h-nav')]//*[@class='word-count' and text()='2']",
]
},
convert_opts: { split_headers: true },
}
)
assert_lib('word count descendant from include without embed includes',
{
convert_dir: true,
filesystem: {
'README.bigb': `= h1
11 22 33
\\Include[notindex]
`,
'notindex.bigb': `= Notindex
44 55
`
},
assert_xpath: {
'index.html': [
"//*[contains(@class, 'h-nav')]//*[contains(@class, 'word-count') and text()='3']",
"//*[contains(@class, 'h-nav')]//*[contains(@class, 'word-count-descendant') and text()='5']",
]
},
}
)
// Toc
// https://github.com/ourbigbook/ourbigbook/issues/143
assert_lib_ast('header with insane paragraph in the content does not blow up',
`\\H[1][a
b]
`,
[
a('H', undefined, {
level: [t('1')],
title: [
a('P', [t('a')]),
a('P', [t('b')]),
]
},
{ id: 'a-b' }
)
]
)
assert_lib_ast('xss: H id',
`= tmp
{id=&\\<>"'}
`,
undefined,
{
assert_xpath_stdout: [
"//x:div[contains(@class, \"h \") and @id=concat('&<>\"', \"'\")]",
]
}
)
// Table of contents
assert_lib_ast('toc: split headers have correct table of contents',
`= h1
== h1 1
== h1 2
=== h1 2 1
==== h1 2 1 1
`,
[
a('H', undefined, {level: [t('1')], title: [t('h1')]}),
a('H', undefined, {level: [t('2')], title: [t('h1 1')]}),
a('H', undefined, {level: [t('2')], title: [t('h1 2')]}),
a('H', undefined, {level: [t('3')], title: [t('h1 2 1')]}),
a('H', undefined, {level: [t('4')], title: [t('h1 2 1 1')]}),
],
{
assert_xpath_stdout: [
// There is a self-link to the Toc.
"//*[@id='_toc']",
"//*[@id='_toc']//x:a[@href='#_toc' and text()=' Table of contents']",
// ToC links have parent toc entry links.
// Toplevel entries point to the ToC toplevel.
`//*[@id='_toc']//*[@id='_toc/h1-1']//x:a[@href='#_toc' and text()=' h1']`,
`//*[@id='_toc']//*[@id='_toc/h1-2']//x:a[@href='#_toc' and text()=' h1']`,
// Inner entries point to their parent entries.
`//*[@id='_toc']//*[@id='_toc/h1-2-1']//x:a[@href='#_toc/h1-2' and text()=' h1 2']`,
// The ToC numbers look OK.
"//*[@id='_toc']//x:a[@href='#h1-2' and text()='2. h1 2']",
// The headers have ToC links.
`${xpath_header(2, 'h1-1')}//x:a[@href='#_toc/h1-1' and @class='toc']`,
`${xpath_header(2, 'h1-2')}//x:a[@href='#_toc/h1-2' and @class='toc']`,
`${xpath_header(3, 'h1-2-1')}//x:a[@href='#_toc/h1-2-1' and @class='toc']`,
// Descendant count.
"//*[@id='_toc']//*[@class='title-div']//*[@class='descendant-count' and text()='4']",
"//*[@id='_toc']//*[@id='_toc/h1-2']//*[@class='descendant-count' and text()='2']",
],
assert_xpath: {
'notindex-split.html': [
// Split output files get their own ToCs.
"//*[@id='_toc']",
"//*[@id='_toc']//x:a[@href='#_toc' and text()=' Table of contents']",
],
'h1-2.html': [
// Split output files get their own ToCs.
"//*[@id='_toc']",
"//*[@id='_toc']//x:a[@href='#_toc' and text()=' Table of contents']",
// The Toc entries of split output headers automatically cull out a level
// of the full number tree. E.g this entry is `2.1` on the toplevel ToC,
// but on this sub-ToC it is just `1.`.
"//*[@id='_toc']//x:a[@href='notindex.html#h1-2-1' and text()='1. h1 2 1']",
"//*[@id='_toc']//x:a[@href='notindex.html#h1-2-1-1' and text()='1.1. h1 2 1 1']",
// We have gone a bit back and forth on split vs nosplit here.
// Related: https://github.com/ourbigbook/ourbigbook/issues/146
`//*[@id='_toc']//*[@id='_toc/h1-2-1']//x:a[@href='#_toc' and text()=' h1 2']`,
`//*[@id='_toc']//*[@id='_toc/h1-2-1-1']//x:a[@href='#_toc/h1-2-1' and text()=' h1 2 1']`,
// Descendant count.
"//*[@id='_toc']//*[@class='title-div']//*[@class='descendant-count' and text()='2']",
"//*[@id='_toc']//*[@id='_toc/h1-2-1']//*[@class='descendant-count' and text()='1']",
],
},
assert_not_xpath: {
// A node without no children headers has no ToC,
// as it would just be empty and waste space.
'h1-2-1-1.html': ["//*[text()=' Table of contents']"],
},
convert_opts: { split_headers: true },
input_path_noext: 'notindex',
},
)
assert_lib_error('toc: _toc is a reserved id',
`= h1
== toc
{id=_toc}
`,
3, 1);
assert_lib('toc: table of contents contains included headers numbered without embed includes',
{
convert_dir: true,
convert_opts: { split_headers: true },
filesystem: {
'notindex.bigb': `= Notindex
\\Q[\\x[notindex2]{full}]
\\Include[notindex2]
== Notindex h2
`,
'notindex2.bigb': `= Notindex2
== Notindex2 h2
== Notindex2 h3
\\Include[notindex3]
`,
'notindex3.bigb': `= Notindex3
== Notindex3 h2
== Notindex3 h3
`,
},
assert_xpath: {
'notindex.html': [
"//x:blockquote//x:a[@href='notindex2.html' and text()='Section 1. \"Notindex2\"']",
"//*[@id='_toc']//x:a[@href='notindex2.html' and @data-test='0' and text()='1. Notindex2']",
"//*[@id='_toc']//x:a[@href='notindex2.html#notindex2-h2' and @data-test='1' and text()='1.1. Notindex2 h2']",
"//*[@id='_toc']//x:a[@href='notindex2.html#notindex2-h3' and @data-test='2' and text()='1.2. Notindex2 h3']",
"//*[@id='_toc']//x:a[@href='notindex3.html' and @data-test='3' and text()='1.2.1. Notindex3']",
"//*[@id='_toc']//x:a[@href='notindex3.html#notindex3-h2' and @data-test='4' and text()='1.2.1.1. Notindex3 h2']",
"//*[@id='_toc']//x:a[@href='notindex3.html#notindex3-h3' and @data-test='5' and text()='1.2.1.2. Notindex3 h3']",
"//*[@id='_toc']//x:a[@href='#notindex-h2' and @data-test='6' and text()='2. Notindex h2']",
],
'notindex-split.html': [
// Links to external source files keep the default split just like regular links.
"//*[@id='_toc']//x:a[@href='notindex2.html' and text()='1. Notindex2']",
"//*[@id='_toc']//x:a[@href='notindex2.html#notindex2-h2' and text()='1.1. Notindex2 h2']",
"//*[@id='_toc']//x:a[@href='notindex.html#notindex-h2' and text()='2. Notindex h2']",
],
},
},
)
assert_lib('toc: table of contents respects numbered=0 of included headers',
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
\\Include[notindex2]
== Notindex h2
`,
'notindex2.bigb': `= Notindex2
{numbered=0}
== Notindex2 h2
`,
},
assert_xpath: {
'notindex.html': [
"//*[@id='_toc']//x:a[@href='notindex2.html' and text()='1. Notindex2']",
"//*[@id='_toc']//x:a[@href='notindex2.html#notindex2-h2' and text()='Notindex2 h2']",
"//*[@id='_toc']//x:a[@href='#notindex-h2' and text()='2. Notindex h2']",
],
},
},
)
if (false) {
// Not implemented yet.
assert_lib('toc: json: table of contents respects tocMaxCrossSource',
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex h1
== Notindex h2
\\Include[notindex2]
=== Notindex h3
`,
'notindex2.bigb': `= Notindex2
{numbered=0}
== Notindex2 h2
=== Notindex2 h3
==== Notindex2 h4
===== Notindex2 h5
`,
},
assert_xpath: {
'notindex.html': [
"//*[@id='_toc']//x:a[@href='#notindex-h2' and text()='1. Notindex h2']",
//"//*[@id='_toc']//x:a[@href='#notindex-h2' and text()='2. Notindex h2']",
//"//*[@id='_toc']//x:a[@href='notindex2.html#notindex2-h2' and text()='Notindex2 h2']",
//"//*[@id='_toc']//x:a[@href='notindex2.html' and text()='1.1. Notindex h3']",
],//
},
},
)
}
assert_lib('toc: table of contents include placeholder header has no number when under numbered=0',
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
{numbered=0}
\\Q[\\x[notindex2]{full}]
\\Include[notindex2]
== Notindex h2
`,
'notindex2.bigb': `= Notindex2
== Notindex2 h2
`,
},
assert_xpath: {
'notindex.html': [
"//x:blockquote//x:a[@href='notindex2.html' and text()='Section \"Notindex2\"']",
"//*[@id='_toc']//x:a[@href='notindex2.html' and text()='Notindex2']",
"//*[@id='_toc']//x:a[@href='notindex2.html#notindex2-h2' and text()='1. Notindex2 h2']",
"//*[@id='_toc']//x:a[@href='#notindex-h2' and text()='Notindex h2']",
],
},
},
)
assert_lib('toc: table of contents does not show synonyms of included headers',
{
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
\\Include[notindex2]
`,
'notindex2.bigb': `= Notindex2
== Notindex2 h2
= Notindex2 h2 synonym
{synonym}
== Notindex2 h2 2
`,
},
//assert_xpath: {
// 'notindex.html': [
// "//*[@id='_toc']//x:a[@href='notindex2.html' and text()='1. Notindex2']",
// "//*[@id='_toc']//x:a[@href='notindex2.html#notindex2-h2' and text()='1.1. Notindex2 h2']",
// "//*[@id='_toc']//x:a[@href='notindex2.html#notindex2-h2-2' and text()='1.2. Notindex2 h2 2']",
// ],
//},
//assert_not_xpath: {
// 'notindex.html': [
// "//*[@id='_toc']//x:a[contains(text(),'synonym')]",
// ],
//},
},
)
assert_lib('toc: header numbered=0 in ourbigbook.json works across source files and on table of contents',
{
convert_dir: true,
convert_opts: {
split_headers: true,
ourbigbook_json: { h: { numbered: false } }
},
filesystem: {
'README.bigb': `= Index
\\Include[notindex]
== H2
`,
'notindex.bigb': `= Notindex
== Notindex h2
`,
},
assert_xpath: {
'index.html': [
"//*[@id='_toc']//x:a[@href='notindex.html' and text()='Notindex']",
"//*[@id='_toc']//x:a[@href='notindex.html#notindex-h2' and text()='Notindex h2']",
"//*[@id='_toc']//x:a[@href='#h2' and text()='H2']",
],
'notindex.html': [
"//*[@id='_toc']//x:a[@href='#notindex-h2' and text()='Notindex h2']",
],
},
},
)
assert_lib('toc: split header with an include and no headers has a single table of contents',
// At 074bacbdd3dc9d3fa8dafec74200043f42779bec was getting two.
{
convert_dir: true,
convert_opts: {
split_headers: true,
ourbigbook_json: { h: { numbered: false } }
},
filesystem: {
'index.bigb': `= Index
\\Include[notindex]
`,
'notindex.bigb': `= Notindex
`,
},
assert_xpath: {
'split.html': [
"//*[@id='_toc']",
],
},
},
)
assert_lib('toc: toplevel scope gets removed on table of contents of included headers',
{
convert_dir: true,
convert_opts: { split_headers: true },
filesystem: {
'index.bigb': `= Index
\\Q[\\x[notindex/notindex-h2]{full}]
\\Include[notindex]
`,
'notindex.bigb': `= Notindex
{scope}
== Notindex h2
`,
},
assert_xpath: {
'index.html': [
"//x:blockquote//x:a[@href='notindex.html#notindex-h2' and text()='Section 1.1. \"Notindex h2\"']",
"//*[@id='_toc']//x:a[@href='notindex.html' and text()='1. Notindex']",
"//*[@id='_toc']//x:a[@href='notindex.html#notindex-h2' and text()='1.1. Notindex h2']",
],
'split.html': [
"//*[@id='_toc']//x:a[@href='notindex.html' and text()='1. Notindex']",
"//*[@id='_toc']//x:a[@href='notindex.html#notindex-h2' and text()='1.1. Notindex h2']",
],
},
},
)
assert_lib_ast('toc: the toc is added before the first h1 when there are multiple toplevel h1',
`aa
= h1
= h2
`,
[
a('P', [t('aa')]),
a('H', undefined, {level: [t('1')], title: [t('h1')]}),
a('H', undefined, {level: [t('1')], title: [t('h2')]}),
],
{
assert_xpath_stdout: [
"//x:div[@class='p' and text()='aa']",
"//*[@id='_toc']",
xpath_header(1, 'h1', undefined, { hasToc: true }),
xpath_header(1, 'h2', undefined, { hasToc: false }),
],
}
)
assert_lib_ast('toc: the toc is added before the first h2 when there is a single h1 and a single h2',
`= h1
== h2
`,
undefined,
{
assert_xpath_stdout: [
"//*[@id='_toc']",
xpath_header(1, 'h1', undefined, { hasToc: false }),
xpath_header(2, 'h2', undefined, { hasToc: true }),
],
}
)
assert_lib_ast('toc: the toc is added before the first h2 when there is a single h1 and a two h2',
`= h1
== h2
== h2 2
`,
undefined,
{
assert_xpath_stdout: [
"//*[@id='_toc']",
xpath_header(1, 'h1', undefined, { hasToc: false }),
xpath_header(2, 'h2', undefined, { hasToc: true }),
xpath_header(2, 'h2-2', undefined, { hasToc: false }),
],
}
)
assert_lib('toc: ancestors list shows after toc on toplevel',
{
filesystem: {
'index.bigb': `= Index
\\Include[notindex]
== h2
=== h3
==== h4
`,
'notindex.bigb': `= Notindex
\\Include[notindex2]
`,
'notindex2.bigb': `= Notindex 2
\\Include[notindex3]
`,
'notindex3.bigb': `= Notindex 2
`
},
convert_dir: true,
convert_opts: { split_headers: true },
assert_xpath: {
'h2.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html']`,
],
'h3.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html#h2']`,
],
'h4.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html#h2']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html#h3']`,
],
'notindex.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html']`,
],
'notindex2.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='notindex.html']`,
],
'notindex3.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='notindex.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='notindex2.html']`,
],
},
assert_not_xpath: {
'index.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']`,
],
},
}
)
// Math.
// \M
// Minimal testing since this is mostly factored out with code tests.
assert_lib_ast('math: inline sane',
'\\m[[\\sqrt{1 + 1}]]\n',
[a('P', [a('m', [t('\\sqrt{1 + 1}')])])],
)
assert_lib_ast('math: inline insane simple',
'$\\sqrt{1 + 1}$\n',
[a('P', [a('m', [t('\\sqrt{1 + 1}')])])],
)
assert_lib_ast('math: inline escape dollar',
'a \\$b c\n',
[a('P', [t('a $b c')])],
)
assert_lib_ast('math: block sane',
'\\M[[\\sqrt{1 + 1}]]',
[a('M', [t('\\sqrt{1 + 1}')])],
)
assert_lib_ast('math: block insane',
'$$\\sqrt{1 + 1}$$',
[a('M', [t('\\sqrt{1 + 1}')])],
)
assert_lib_stdin('math: define and use in another block with split headers',
// Can lead to double redefinition errors if we are not careful on implementation.
`$$
\\newcommand{\\mycmd}[0]{hello}
$$
$$
\\mycmd
$$
`,
{
convert_opts: { split_headers: true },
}
)
assert_lib_stdin('math block with comment on last line',
// KaTeX parse error: LaTeX-incompatible input and strict mode is set to 'error': % comment has no terminating newline; LaTeX would fail because of commenting the end of math mode (e.g. $) [commentAtEnd]
`$$
% my comment
$$
`,
)
assert_lib_error('math undefined macro', '\\m[[\\reserved_undefined]]', 1, 3);
assert_lib_ast('math: with description has caption',
`$$
aa
$$
{description=b b}
`,
[
a('M', [t('aa')], { description: [t('b b')] }, { id: '_1'} ),
],
{
assert_xpath_stdout: [
"//x:span[@class='caption-prefix' and text()='Equation 1']",
]
}
)
// Quote.
// \Q
assert_lib_stdin('quotation: generates valid HTML with title',
`\\Q[My quote]{title=My title}
`,
{
assert_xpath_stdout: [
`//x:div[@id='quote-my-title']//x:blockquote[text()='My quote']`,
],
}
)
// Include.
const include_opts = {
convert_opts: {
embed_includes: true,
}
};
const include_two_levels_ast_args = [
a('H', undefined, {level: [t('2')], title: [t('ee')]}),
a('P', [t('ff')]),
a('H', undefined, {level: [t('3')], title: [t('gg')]}),
a('P', [t('hh')]),
]
assert_lib_ast('include: simple with paragraph with embed includes',
`= Index
Index paragraph.
\\Include[include-one-level-1]
\\Include[include-one-level-2]
`,
[
a('H', undefined, {level: [t('1')], title: [t('Index')]}),
a('P', [t('Index paragraph.')]),
a('H', undefined, {level: [t('2')], title: [t('Include one level 1')]}),
a('P', [t('Include one level 1 paragraph.')]),
a('H', undefined, {level: [t('2')], title: [t('Include one level 2')]}),
a('P', [t('Include one level 2 paragraph.')]),
],
{
convert_opts: {
embed_includes: true,
},
filesystem: default_filesystem2,
assert_xpath_stdout: [
"//x:div[@class='p' and text()='Include one level 1 paragraph.']",
],
},
)
assert_lib_ast('include: parent argument with embed includes',
`= h1
== h2
\\Include[include-one-level-1]{parent=h1}
`,
[
a('H', undefined, {level: [t('1')], title: [t('h1')]}),
a('H', undefined, {level: [t('2')], title: [t('h2')]}),
// This is level 2, not three, since it's parent is h1.
a('H', undefined, {level: [t('2')], title: [t('cc')]}),
a('P', [t('dd')]),
],
include_opts
)
assert_lib_error('include: parent argument to old ID fails gracefully',
`= h1
== h2
== h2 2
\\Include[include-one-level-1]{parent=h2}
`,
7, 30, undefined, include_opts,
)
assert_lib_ast('include: simple without parent in the include with embed includes',
`= aa
bb
\\Include[include-two-levels]
`,
[
a('H', undefined, {level: [t('1')], title: [t('aa')]}),
a('P', [t('bb')]),
a('H', undefined, {level: [t('2')], title: [t('ee')]}),
a('P', [t('ff')]),
a('H', undefined, {level: [t('3')], title: [t('gg')]}),
a('P', [t('hh')]),
],
include_opts
)
assert_lib_ast('include: simple with parent in the include with embed includes',
`= aa
bb
\\Include[include-two-levels-parent]
`,
[
a('H', undefined, {level: [t('1')], title: [t('aa')]}),
a('P', [t('bb')]),
a('H', undefined, {level: [t('2')], title: [t('Include two levels parent')]}),
a('P', [t('h1 content')]),
a('H', undefined, {level: [t('3')], title: [t('Include two levels parent h2')]}),
a('P', [t('h2 content')]),
],
include_opts
)
assert_lib_ast('include: simple with paragraph with no embed includes',
`= Notindex
bb
\\Include[notindex2]
`,
[
a('H', undefined, {level: [t('1')], title: [t('Notindex')]}),
a('P', [t('bb')]),
a('H', undefined, {level: [t('2')], title: [t('Notindex2')]}),
a('P', [
a(
'x',
[t('This section is present in another page, follow this link to view it.')],
{'href': [t('notindex2')]}
),
]),
],
{
convert_before: ['notindex2.bigb'],
convert_opts: { split_headers: true },
filesystem: {
'notindex2.bigb': `= Notindex2
`,
},
assert_xpath_stdout: [
xpath_header(1, 'notindex', "x:a[@href='notindex-split.html' and text()='Notindex']"),
xpath_header(2, 'notindex2', "x:a[@href='notindex2.html' and text()='1. Notindex2']"),
],
input_path_noext: 'notindex',
},
)
// https://github.com/ourbigbook/ourbigbook/issues/74
assert_lib_ast('include: cross reference to embed include header',
`= aa
\\x[include-two-levels]
\\x[gg]
\\Include[include-two-levels]
`,
[
a('H', undefined, {level: [t('1')], title: [t('aa')]}),
a('P', [
a('x', undefined, {href: [t('include-two-levels')]}),
]),
a('P', [
a('x', undefined, {href: [t('gg')]}),
]),
].concat(include_two_levels_ast_args),
Object.assign({
assert_xpath_stdout: [
"//x:div[@class='p']//x:a[@href='#include-two-levels' and text()='ee']",
"//x:div[@class='p']//x:a[@href='#gg' and text()='gg']",
],
convert_opts: { split_headers: true },
},
include_opts
),
)
assert_lib_ast('include: multilevel with paragraph',
`= aa
bb
\\Include[include-two-levels]
\\Include[include-one-level-1]
`,
[
a('H', undefined, {level: [t('1')], title: [t('aa')]}),
a('P', [t('bb')]),
].concat(include_two_levels_ast_args)
.concat([
a('H', undefined, {level: [t('2')], title: [t('cc')]}),
a('P', [t('dd')]),
]),
include_opts
)
// https://github.com/ourbigbook/ourbigbook/issues/35
assert_lib_ast('include: simple no paragraph',
`= aa
bb
\\Include[include-one-level-1]
\\Include[include-one-level-2]
`,
[
a('H', undefined, {level: [t('1')], title: [t('aa')]}),
a('P', [t('bb')]),
a('H', undefined, {level: [t('2')], title: [t('cc')]}),
a('P', [t('dd')]),
a('H', undefined, {level: [t('2')], title: [t('ee')]}),
a('P', [t('ff')]),
],
include_opts
)
assert_lib_ast('include: multilevel no paragraph',
`= aa
bb
\\Include[include-two-levels]
\\Include[include-one-level-1]
`,
[
a('H', undefined, {level: [t('1')], title: [t('aa')]}),
a('P', [t('bb')]),
].concat(include_two_levels_ast_args)
.concat([
a('H', undefined, {level: [t('2')], title: [t('cc')]}),
a('P', [t('dd')]),
]),
include_opts
)
// https://github.com/ourbigbook/ourbigbook/issues/23
assert_lib_error('include: with error reports error on the include source',
`= aa
bb
\\Include[include-with-error]
`,
3, 1, 'include-with-error.bigb',
include_opts
)
const circular_entry = `= notindex
\\Include[include-circular]
`;
assert_lib_error('include: circular dependency 1 <-> 2',
circular_entry,
// TODO works from CLI call......... fuck, why.
// Similar problem as in test below.
//3, 1, 'include-circular.bigb',
undefined, undefined, undefined,
{
convert_opts: {
embed_includes: true,
input_path_noext: 'notindex',
},
has_error: true,
filesystem: {
'notindex.bigb': circular_entry,
'include-circular.bigb': `= include-circular
\\Include[notindex]
`
}
}
)
// TODO error this is legitimately failing on CLI, bad error messages show
// up on CLI reproduction.
// The root problem is that include_path_set does not contain
// include-circular-2.bigb, and that leads to several:
// ```
// file not found on database: "${target_input_path}", needed for toplevel scope removal
// on ToC conversion.
assert_lib_error('include: circular dependency 1 -> 2 <-> 3',
`= aa
\\Include[include-circular-1]
`,
// 3, 1, 'include-circular-2.bigb',
undefined, undefined, undefined,
ourbigbook.cloneAndSet(include_opts, 'has_error', true)
)
assert_lib_ast('include without parent header with embed includes',
// https://github.com/ourbigbook/ourbigbook/issues/73
`\\Include[include-one-level-1]
\\Include[include-one-level-2]
`,
[
// TODO this is what we really want.
//a('Toc'),
a('H', undefined, {level: [t('1')], title: [t('cc')]}),
a('P', [t('dd')]),
a('H', undefined, {level: [t('1')], title: [t('ee')]}),
a('P', [t('ff')]),
],
{
//assert_xpath_stdout: [
// // TODO getting corrupt <hNaN>
// xpath_header(1, 'include-one-level-1'),
// xpath_header(1, 'include-one-level-2'),
//],
convert_opts: {
embed_includes: true,
}
},
)
assert_lib_ast('include: without parent header without embed includes',
// https://github.com/ourbigbook/ourbigbook/issues/73
`aa
\\Include[include-one-level-1]
\\Include[include-one-level-2]
`,
[
a('P', [t('aa')]),
a('H', undefined, {level: [t('1')], title: [t('cc')]}),
a('P', [
a(
'x',
[t('This section is present in another page, follow this link to view it.')],
{'href': [t('include-one-level-1')]}
),
]),
a('H', undefined, {level: [t('1')], title: [t('ee')]}),
a('P', [
a(
'x',
[t('This section is present in another page, follow this link to view it.')],
{'href': [t('include-one-level-2')]}
),
]),
],
{
convert_before: [
'include-one-level-1.bigb',
'include-one-level-2.bigb',
],
assert_xpath_stdout: [
// TODO getting corrupt <hNaN>
//xpath_header(1, 'include-one-level-1'),
//xpath_header(1, 'include-one-level-2'),
],
},
)
assert_lib_error('include: to file that exists in header title fails gracefully',
// https://github.com/ourbigbook/ourbigbook/issues/195
`= tmp
== \\Include[tmp2]
`,
3, 4, 'tmp.bigb',
{
filesystem: {
'tmp2.bigb': `= Tmp2
`
},
convert_before: ['tmp2.bigb'],
input_path_noext: 'tmp',
}
)
assert_lib_error('include: to file that does not exist fails gracefully',
`= h1
\\Include[asdf]
`,
3, 1
)
assert_lib_error('include: to file that does exists without embed includes before extracting IDs fails gracefully',
`= h1
\\Include[asdf]
`,
3, 1, undefined, {
// No error with this.
//convert_before: ['asdf.bigb'],
filesystem: {
'asdf.bigb': '= asdf'
}
}
)
assert_lib('include: relative include in subdirectory',
{
filesystem: {
's1/index.bigb': `= Index
\\Include[notindex]
`,
's1/notindex.bigb': `= Notindex
\\Include[notindex2]
== Notindex h2`,
's1/notindex2.bigb': `= Notindex2
`,
// https://github.com/ourbigbook/ourbigbook/issues/214
'top.bigb': `= Top
`,
},
convert_dir: true,
assert_xpath: {
's1.html': [
"//*[@id='_toc']//x:a[@href='s1/notindex.html' and @data-test='0' and text()='1. Notindex']",
"//*[@id='_toc']//x:a[@href='s1/notindex2.html' and @data-test='1' and text()='1.1. Notindex2']",
"//*[@id='_toc']//x:a[@href='s1/notindex.html#notindex-h2' and @data-test='2' and text()='1.2. Notindex h2']",
// https://github.com/ourbigbook/ourbigbook/issues/214
//"//*[@id='_toc']//x:a[@href='../top.html' and @data-test='2' and text()='2. Top']",
],
},
}
)
assert_lib('include: from parent to subdirectory',
// https://github.com/ourbigbook/ourbigbook/issues/116
{
filesystem: {
'index.bigb': `= Index
\\x[subdir][index to subdir]
\\x[subdir/h2][index to subdir h2]
\\Include[subdir]
\\Include[subdir/notindex]
`,
'subdir/index.bigb': `= Index
== h2
`,
'subdir/notindex.bigb': `= Notindex
== Notindex h2
`,
},
convert_dir: true,
assert_xpath: {
'index.html': [
"//x:a[@href='subdir.html' and text()='index to subdir']",
"//x:a[@href='subdir.html#h2' and text()='index to subdir h2']",
],
},
}
)
assert_lib('include: subdir index.bigb outputs to subdir without trailing slash with htmlXExtension=true',
{
filesystem: {
'subdir/index.bigb': `= Subdir
\\x[subdir/notindex][link to subdir notindex]
\\x[subdir/notindex-h2][link to subdir notindex h2]
`,
'subdir/notindex.bigb': `= Notindex
== Notindex h2
`,
},
convert_dir: true,
convert_opts: { htmlXExtension: true },
assert_xpath: {
'subdir.html': [
"//x:a[@href='subdir/notindex.html' and text()='link to subdir notindex']",
"//x:a[@href='subdir/notindex.html#notindex-h2' and text()='link to subdir notindex h2']" ,
],
},
}
)
assert_lib('include: subdir index.bigb outputs to subdir without trailing slash with htmlXExtension=false',
{
filesystem: {
'subdir/index.bigb': `= Subdir
\\x[subdir/notindex][link to subdir notindex]
\\x[subdir/notindex-h2][link to subdir notindex h2]
`,
'subdir/notindex.bigb': `= Notindex
== Notindex h2
`,
},
convert_dir: true,
convert_opts: { htmlXExtension: false },
assert_xpath: {
'subdir.html': [
"//x:a[@href='subdir/notindex' and text()='link to subdir notindex']",
"//x:a[@href='subdir/notindex#notindex-h2' and text()='link to subdir notindex h2']",
],
},
}
)
assert_lib('include: subdir index.bigb removes leading @ from links with the x_remove_leading_at option',
{
filesystem: {
'@subdir/index.bigb': `= Subdir
\\x[notindex][link to subdir notindex]
\\x[notindex-h2][link to subdir notindex h2]
<@subdir/notindex>
\\Include[notindex]
`,
'@subdir/notindex.bigb': `= Notindex
\\x[@subdir][link to subdir]
== Notindex h2
`,
'@subdir/@notindexat.bigb': `= Notindexat
== Notindexat h2
`,
},
convert_dir: true,
convert_opts: {
x_remove_leading_at: true,
x_leading_at_to_web: false,
},
assert_xpath: {
'@subdir.html': [
"//x:a[@href='subdir/notindex.html' and text()='link to subdir notindex']",
"//x:a[@href='subdir/notindex.html#notindex-h2' and text()='link to subdir notindex h2']" ,
],
'@subdir/notindex.html': [
"//x:a[@href='../subdir.html' and text()='link to subdir']",
xpath_header_parent(1, 'notindex', '../subdir.html', 'Subdir'),
],
},
}
)
assert_lib('include: subdir index.bigb outputs to subdir.html when there is a toplevel header',
{
filesystem: {
'subdir/index.bigb': `= Subdir
Hello world
`,
},
convert_dir: true,
assert_xpath: {
'subdir.html': [
"//x:div[@class='p' and text()='Hello world']",
],
},
}
)
assert_lib('include: subdir index.bigb outputs to subdir.html when there is no toplevel header',
// https://github.com/ourbigbook/ourbigbook/issues/247
{
filesystem: {
'subdir/index.bigb': `Hello world
`,
},
convert_dir: true,
assert_xpath: {
'subdir.html': [
"//x:div[@class='p' and text()='Hello world']",
],
},
}
)
assert_lib('include: include of a header with a tag or child in a third file does not blow up',
{
filesystem: {
'index.bigb': `= Index
\\Include[notindex]
`,
'notindex.bigb': `= Notindex
{child=notindex2}
{tag=notindex2}
`,
'notindex2.bigb': `= Notindex 2
`,
},
convert_dir: true,
}
)
assert_cli('include: tags show on embed include',
{
args: ['--embed-includes', 'index.bigb'],
pre_exec: [
{ cmd: ['ourbigbook', ['.']], },
],
filesystem: {
'index.bigb': `= Index
\\Include[notindex]
`,
'notindex.bigb': `= Notindex
{tag=notindex2}
`,
'notindex2.bigb': `= Notindex 2
`,
},
// TODO fails on lib with duplicate id notindex.
//
// This started happening when we moved Id.path from a string
// to Id.defined_at as a reference to a File.
//
// Apparently this fails because of convert_dir which
// creates the ID, and then things don't get cleaned up.
// But works on CLI, so not worrying for now.
//convert_dir: true,
//convert_opts: {
// embed_includes: true,
//},
assert_xpath: {
'out/html/index.html': [
"//*[contains(@class, 'h-nav')]//x:span[@class='tags']//x:a[@href='notindex2.html']",
],
},
}
)
assert_lib(
// https://github.com/ourbigbook/ourbigbook/issues/123
'include: includers should show as a parents of the includee',
{
convert_dir: true,
filesystem: {
'README.bigb': `= Index
\\Include[included-by-index]
`,
'not-readme.bigb': `= Not readme
\\Include[included-by-index]
`,
'included-by-index.bigb': `= Included by index
`,
},
assert_xpath: {
'included-by-index.html': [
xpath_header_parent(1, 'included-by-index', 'index.html', 'Index'),
// Multiple includers showing on parent used to work. But we killed it when we added breadcrumbs
// at 7e571ee7f8a2e1af30fccbf51029ce51d7cca529
// If we were to revive this, we should likely have a separate meta line for each includer.
// But lazy, especially considering that this construct do not work on OurBigBook Web and
// should likely be linted out by default instead.
//xpath_header_parent(1, 'included-by-index', 'not-readme.html', 'Not readme'),
],
}
}
)
assert_lib(
'include: incoming links: does not generate an incoming links entry',
{
convert_dir: true,
convert_opts: {
split_headers: true,
},
filesystem: {
'README.bigb': `= Index
\\Include[included-by-index]
`,
'included-by-index.bigb': `= Included by index
`,
},
assert_not_xpath: {
'included-by-index.html': [
`//x:h2[@id='_incoming-links']`,
],
}
}
)
assert_lib('include: parent_id option',
{
filesystem: {
'notindex.bigb': `= Notindex
`,
'notindex2.bigb': `= Notindex2
`,
},
convert_before: [
'notindex2.bigb',
],
// This setup is to not render notindex.bigb with that parent_id, otherwise we get an infinite loop.
convert_before_norender: [ 'notindex.bigb' ],
convert_opts: { parent_id: 'notindex' },
assert_xpath: {
'notindex2.html': [
xpath_header_parent(1, 'notindex2', 'notindex.html', 'Notindex'),
],
},
}
)
// OurBigBookExample
assert_lib_ast('OurBigBookExample basic',
`\\OurBigBookExample[[aa \\i[bb] cc]]`,
[
// TODO get rid of this paragaraph.
a('P', [
a('C', [t('aa \\i[bb] cc')]),
a('P', [t('which renders as:')]),
a('Q', [
// TODO get rid of this paragaraph.
a('P', [
t('aa '),
a('i', [t('bb')]),
t(' cc'),
])
]),
])
],
)
assert_lib('OurBigBookExample that links to id in another file',
{
filesystem: {
'abc.bigb': `\\OurBigBookExample[[\\x[notindex\\]]]
`,
'notindex.bigb': `= notindex h1
`,
},
convert_dir: true,
assert_xpath: {
'abc.html': [
"//x:a[@href='notindex.html' and text()='notindex h1']",
],
},
},
)
// passthrough
assert_lib('passthrough: basic',
{
convert_dir: true,
filesystem: {
'index.bigb': `\\passthrough[[<div id="my-passthrough"></div>]]
`,
},
assert_xpath: {
'index.html': [
"//x:div[@id='my-passthrough']",
],
},
}
)
assert_lib('passthrough: xss_safe',
{
convert_dir: true,
convert_opts: { xss_safe: true },
filesystem: {
'index.bigb': `\\passthrough[[<div id="my-passthrough"></div>]]
`,
},
assert_xpath: {
'index.html': [
`//x:pre//x:code[text()='<div id="my-passthrough"></div>']`,
],
},
}
)
// ID auto-generation.
// https://docs.ourbigbook.com/automatic-id-from-title
assert_lib_ast('id autogeneration without title',
'\\P[aa]\n',
[a('P', [t('aa')], {}, {id: '_1'})],
)
assert_lib_error('id conflict with previous autogenerated id',
`\\P[aa]
\\P[bb]{id=_1}`,
3, 1
)
assert_lib_error('id conflict with later autogenerated id',
`\\P[aa]{id=_1}
\\P[bb]`,
1, 1
)
assert_lib_error('id cannot be empty',
`= Index
==
`,
3, 1
)
// https://github.com/ourbigbook/ourbigbook/issues/4
assert_lib_ast('id autogeneration nested',
'\\Q[\\P[aa]]\n\n\\P[bb]\n',
[
a('Q', [
a('P', [t('aa')], {}, {id: '_2'})
],
{},
{id: '_1'}
),
a('P', [t('bb')], {}, {id: '_3'}),
],
)
assert_lib_ast('id autogeneration unicode normalize',
// 00C9: Latin capital letter E with acute
// 00DE: capital Thorn: https://en.wikipedia.org/wiki/Thorn_(letter)
// 0141: capital L with a stroke: https://en.wikipedia.org/wiki/%C5%81
// 0152: Latin capital ligature oe: https://en.wikipedia.org/wiki/%C5%92
// 0152: capital ligature oe (ethel): https://en.wikipedia.org/wiki/%C5%92
// 0391: capital alpha: https://en.wikipedia.org/wiki/Alpha[].
// 03B1: lowercase alpha
// 2013: en-dash -> -
// 2014: em-dash -> -
// 2212: Unicode minus sign
// 4F60: Chinese ni3 (you): https://en.wiktionary.org/wiki/%E4%BD%A0
`= 0\u{2013}A.\u{4F60}\u{00C9}\u{2014}\u{0141}\u{0152}y++z\u{00DE}\u{0391}\u{2212}
\\x[0-a-\u{4F60}e-loey-plus-plus-zth-alpha-minus]
`,
[
a('H', undefined, {title: [t('0\u{2013}A.\u{4F60}\u{00C9}\u{2014}\u{0141}\u{0152}y++z\u{00DE}\u{0391}\u{2212}')]}, {id: '0-a-\u{4F60}e-loey-plus-plus-zth-alpha-minus'}),
a('P', [
a('x', undefined, {href: [t('0-a-\u{4F60}e-loey-plus-plus-zth-alpha-minus')]})
])
],
)
assert_lib_ast('id autogeneration unicode no normalize',
`= 0A.你ÉŁŒy++z
\\x[0a-你éłœy-z]
`,
[
a('H', undefined, {title: [t('0A.你ÉŁŒy++z')]}, {id: '0a-你éłœy-z'}),
a('P', [
a('x', undefined, {href: [t('0a-你éłœy-z')]})
])
],
{ convert_opts: { ourbigbook_json: { id: { normalize: { latin: false, punctuation: false } } } } }
)
assert_lib_ast('id autogeneration with disambiguate',
`= ab
{disambiguate=cd}
\\x[ab-cd]
`,
[
a('H', undefined, {title: [t('ab')], disambiguate: [t('cd')]}, {id: 'ab-cd'}),
a('P', [
a('x', undefined, {href: [t('ab-cd')]})
])
],
)
assert_lib_error('id autogeneration with undefined reference in title fails gracefully',
`= \\x[reserved_undefined]
`, 1, 3);
// https://github.com/ourbigbook/ourbigbook/issues/45
assert_lib_ast('id autogeneration with nested elements does an id conversion and works',
`= ab \`cd\` ef
\\x[ab-cd-ef]
`,
[
a(
'H',
undefined,
{
level: [t('1')],
title: [
t('ab '),
a('c', [t('cd')]),
t(' ef'),
],
},
{
id: 'ab-cd-ef',
}
),
a('P', [
a('x', undefined, { href: [t('ab-cd-ef')]}),
]),
]
)
// ID conflicts.
assert_lib_error('id conflict with previous id on the same file',
`= tmp
{id=tmp}
== tmp
`,
4, 1, 'index.bigb',
{
error_message: ourbigbook.duplicateIdErrorMessage('tmp', 'index.bigb', 1, 1),
input_path_noext: 'index',
},
)
assert_lib_ast('id conflict with id on another file simple',
// https://github.com/ourbigbook/ourbigbook/issues/201
`= index
== notindex h2
`,
undefined,
{
convert_before: ['notindex.bigb'],
duplicate_ids: [
['notindex-h2', 'index.bigb', 3, 1],
['notindex-h2', 'notindex.bigb', 3, 1],
],
filesystem: {
'notindex.bigb': `= notindex
== notindex h2
`,
},
input_path_noext: 'index'
}
)
assert_lib_ast('id conflict with id on another file where conflict header has a child header',
// Bug introduced at ef9e2445654300c4ac41e1d06d3d2a1889dd0554
`= tmp
== aa
`,
undefined,
{
convert_before: ['tmp2.bigb'],
duplicate_ids: [
['aa', 'tmp.bigb', 3, 1],
['aa', 'tmp2.bigb', 3, 1],
],
filesystem: {
'tmp2.bigb': `= tmp2
== aa
=== bb
`,
},
input_path_noext: 'tmp'
}
)
assert_lib_error('id conflict on file with the same toplevel_id as another',
undefined,
1, 1, 'index.bigb',
{
convert_before_norender: ['notindex.bigb'],
convert_before: ['index.bigb'],
filesystem: {
'index.bigb': `= Notindex
`,
'notindex.bigb': `= Notindex
`,
},
}
)
// titleToId
assert_equal('titleToId with hyphen', ourbigbook.titleToId('.0A. - z.a Z..'), '0a-z-a-z');
assert_equal('titleToId with unicode chars', ourbigbook.titleToId('0A.你好z'), '0a-你好z');
// Toplevel.
assert_lib_ast('toplevel: arguments',
`{title=aaa}
bbb
`,
a('Toplevel', [a('P', [t('bbb')])], {'title': [t('aaa')]}),
{toplevel: true}
)
assert_lib_error('toplevel explicit content',
`[]`, 1, 1,
)
// https://github.com/ourbigbook/ourbigbook/issues/10
assert_lib_error('explicit toplevel macro',
`\\toplevel`, 1, 1,
)
// split_headers
// A split headers hello world.
assert_lib_ast('one paragraph implicit split headers',
'ab\n',
[a('P', [t('ab')])],
{
convert_opts: { split_headers: true },
input_path_noext: 'notindex',
}
)
// Errors. Check that they return gracefully with the error line number,
// rather than blowing up an exception, or worse, not blowing up at all!
assert_lib_ast('backslash without macro', '\\ a', [a('P', [t(' a')])],);
assert_lib_error('unknown macro without args', '\\reserved_undefined', 1, 1);
assert_lib_error('unknown macro with positional arg', '\\reserved_undefined[aa]', 1, 1);
assert_lib_error('unknown macro with named arg', '\\reserved_undefined{aa=bb}', 1, 1);
assert_lib_error('too many positional arguments', '\\P[ab][cd]', 1, 7);
assert_lib_error('unknown named macro argument', '\\c{reserved_undefined=abc}[]', 1, 4);
assert_lib_error('missing mandatory positional argument href of a', '\\a', 1, 1);
assert_lib_error('missing mandatory positional argument level of h', '\\H', 1, 1);
assert_lib_error('stray open positional argument start', 'a[b\n', 1, 2);
assert_lib_error('stray open named argument start', 'a{b\n', 1, 2);
assert_lib_error('argument without close empty', '\\c[\n', 1, 3);
assert_lib_error('argument without close nonempty', '\\c[ab\n', 1, 3);
assert_lib_error('stray positional argument end', 'a]b', 1, 2);
assert_lib_error('stray named argument end}', 'a}b', 1, 2);
assert_lib_error('unterminated literal positional argument', '\\c[[\n', 1, 3);
assert_lib_error('unterminated literal named argument', '\\Image[img.png]{external}{description=\n', 1, 26);
assert_lib_error('unterminated insane inline code', '`\n', 1, 1);
assert_lib_error('unterminated insane link', '<ab', 1, 1);
assert_lib_error('unescaped trailing backslash', '\\', 1, 1);
// API minimal tests.
it(`lib: x does not blow up without ID provider`, async function () {
const out = await ourbigbook.convert(`= h1
\\x[h2]
== h2
`, {'body_only': true})
})
// TODO
const bigb_input = fs.readFileSync(path.join(__dirname, 'test_bigb_output.bigb'), ourbigbook_nodejs_webpack_safe.ENCODING)
assert_lib('bigb output: format is unchanged for the preferred format',
// https://github.com/ourbigbook/ourbigbook/issues/83
{
stdin: bigb_input,
assert_bigb_stdout: bigb_input,
convert_dir: true,
filesystem: {
'test-bigb-output-2.bigb': '= Test bigb output 2\n',
'test-bigb-output-3.bigb': '= Test bigb output 3\n',
}
},
)
assert_lib_stdin('bigb output: converts plaintext arguments with escapes to literal arguments when possible',
`\\Q[\\\\ \\[ \\] \\{ \\} \\< \\\` \\$]
\\Q[\\* *]
\\Q[\\= =]
\\Q[\\|| ||]
\\Q[\\| |]
\\Q[\\\\ \\[ \\] \\{ \\} \\< \\\` \\$ \\i[asdf]]
\\Q[\\* \\i[asdf]]
\\Q[\\= \\i[asdf]]
\\Q[\\|| \\i[asdf]]
\\Q[\\| \\i[asdf]]
`,
{
assert_bigb_stdout: `\\Q[[\\ [ ] { } < \` $]]
\\Q[[* *]]
\\Q[[= =]]
\\Q[[|| ||]]
\\Q[[| |]]
\\Q[\\\\ \\[ \\] \\{ \\} \\< \\\` \\$ \\i[asdf]]
\\Q[\\* \\i[asdf]]
\\Q[\\= \\i[asdf]]
\\Q[\\|| \\i[asdf]]
\\Q[\\| \\i[asdf]]
`
},
)
assert_lib_stdin('bigb output: converts sane refs to insane ones',
`= Animal
\\x[black-cat]
\\x[black-cat]{c}
\\x[black-cat]{p}
== Black cat
`,
{
assert_bigb_stdout: `= Animal
<black cat>
<Black cat>
<black cats>
== Black cat
`
},
)
assert_lib_stdin('bigb output: adds newlines to start and end of multiline arguments',
`\\Q[Positional oneline first]
\\Q[Positional multiline first
Positional multiline second]
\\Image[a.png]
{description=Named oneline first}
\\Image[a.png]
{description=Named multiline first
Named multiline second}
`,
{
assert_bigb_stdout: `\\Q[Positional oneline first]
\\Q[
Positional multiline first
Positional multiline second
]
\\Image[a.png]
{description=Named oneline first}
\\Image[a.png]
{description=
Named multiline first
Named multiline second
}
`,
filesystem: {
'a.png': '',
}
},
)
assert_lib_stdin('bigb output: nested sane list followed by paragraph',
// This was leading to an AST change because the input has inner list as
// `ccc\n` but the output only `ccc`. But lazy to fix now, what we want is the
// input to parse as `ccc` without the `\n`: https://github.com/ourbigbook/ourbigbook/issues/245
`aaa
* bbb
\\Ul[
* ccc
]
ddd
`,
{
assert_bigb_stdout: `aaa
* bbb
* ccc
ddd
`
},
)
assert_lib('bigb output: checks target IDs to decide between plural or not on converting non magic to magic links',
{
filesystem: {
'index.bigb': `= Index
\\x[dog]
\\x[dog]{p}
`,
'notindex.bigb': `= Notindex
== Dog
== Dogs
`,
},
convert_dir: true,
assert_bigb: {
'index.bigb': `= Index
<dog>
<dog>{p}
`,
}
}
)
assert_lib('bigb output: unused ID check does not blow up across files with magic plural',
{
filesystem: {
'index.bigb': `= Index
<dogs>
`,
'notindex.bigb': `= Notindex
== Dog
`,
},
convert_dir: true,
convert_opts: { output_format: ourbigbook.OUTPUT_FORMAT_OURBIGBOOK },
}
)
assert_lib('bigb output: x uses text conversion as the target link',
{
filesystem: {
'index.bigb': `= Index
\\x[dog-and-cat]{c}{p}
\\x[asdf-asdf]
\\x[matching-id]
\\x[plural-apples]
<plural apples>
<accounts>
`,
'notindex.bigb': `= Notindex
== Dog $and$ Cat
== Qwer Qwer
{id=asdf-asdf}
== Matching ID
{id=matching-id}
== Plural Apples
{id=plural-apples}
`,
'accounts.bigb': `= My accounts
`
},
convert_dir: true,
assert_bigb: {
'index.bigb': `= Index
<Dog and Cats>
<asdf asdf>
<matching ID>
<plural Apples>
<plural Apples>
<accounts>
`,
}
}
)
assert_lib('bigb output: x magic input across files',
{
filesystem: {
'index.bigb': `= Index
<Dog and cat>
<Dog and cats>
<Uppercase>
<uppercase>
<Lowercase>
<lowercase>
<my plurals>
`,
'notindex.bigb': `= Notindex
== Dog and cat
== Uppercase
{c}
== lowercase
{c}
== My plurals
`,
},
convert_dir: true,
assert_bigb: {
'index.bigb': `= Index
<Dog and cat>
<Dog and cats>
<Uppercase>
<Uppercase>
<lowercase>
<lowercase>
<my plurals>
`,
}
}
)
assert_lib('bigb output: x to disambiguate',
{
filesystem: {
'index.bigb': `= Index
\\x[python-animal]
\\x[python-animal]{p}
<python animal>
<Python (animal)>
`,
'notindex.bigb': `= Notindex
== Python
{disambiguate=animal}
`,
},
convert_dir: true,
// TODO maybe https://github.com/ourbigbook/ourbigbook/issues/244
assert_bigb: {
'index.bigb': `= Index
<python (animal)>
\\x[python-animal]{p}
<python (animal)>
<Python (animal)>
`,
}
}
)
assert_lib('bigb output: x to plural disambiguate',
// Happens notably with pluralize false plural bugs such as "Mathematics".
{
filesystem: {
'index.bigb': `= Index
<field cats>
`,
'notindex.bigb': `= Notindex
== Field
{disambiguate=cats}
`,
},
convert_dir: true,
// TODO maybe https://github.com/ourbigbook/ourbigbook/issues/244
assert_bigb: {
'index.bigb': `= Index
<field (cats)>
`,
}
}
)
assert_lib('bigb output: x to scope',
{
filesystem: {
'index.bigb': `= Index
<my dog/pit bull>
<my dog/Pit bull>
<my dog/pit bulls>
<fruit/banana>
<fruit/orange>
== Fruit
{scope}
<banana>
<bananas>
<car/ferrari>
=== Banana
=== Orange
{id=orange}
== Car
{scope}
=== Ferrari
`,
'animal.bigb': `= Animal
== My dog
{scope}
=== Pit bull
`,
},
convert_dir: true,
assert_bigb: {
'index.bigb': `= Index
<my dog/pit bull>
<my dog/Pit bull>
<my dog/pit bulls>
<fruit/banana>
<fruit/orange>
== Fruit
{scope}
<banana>
<bananas>
<car/ferrari>
=== Banana
=== Orange
{id=orange}
== Car
{scope}
=== Ferrari
`,
}
}
)
assert_lib('bigb output: x with leading slash to escape scope',
{
filesystem: {
'index.bigb': `= Index
== Fruit
{scope}
=== Fruit
{scope}
<fruit>
</fruit>
`,
},
convert_dir: true,
assert_bigb: {
'index.bigb': `= Index
== Fruit
{scope}
=== Fruit
{scope}
<fruit>
</fruit>
`,
}
}
)
assert_lib('bigb output: magic x in subdir scope',
{
filesystem: {
'myscope/notindex.bigb': `= Index
<dog>
`,
'myscope/notindex2.bigb': `= Animal
== Dog
`,
},
convert_dir: true,
assert_bigb: {
'myscope/notindex.bigb': `= Index
<dog>
`,
}
}
)
assert_lib('bigb output: magic x to image',
{
filesystem: {
'notindex.bigb': `= Index
<image My dog>
\\Image[dog.jpg]
{title=My dog}
<video My cat>
\\Video[dog.jpg]
{title=My cat}
`,
'dog.jpg': '',
},
convert_dir: true,
assert_bigb: {
'notindex.bigb': `= Index
<image My dog>
\\Image[dog.jpg]
{title=My dog}
<video My cat>
\\Video[dog.jpg]
{title=My cat}
`,
}
}
)
assert_lib('bigb output: x to slash in title',
// We have to remove slashes, otherwise it searches for scopes instead.
// Don't have a solution for that now.
{
filesystem: {
'notindex.bigb': `= Index
<my title>
== My/title
`,
},
convert_dir: true,
assert_bigb: {
'notindex.bigb': `= Index
<my title>
== My/title
`,
}
}
)
assert_lib('bigb output: id from filename',
// We have to remove slashes, otherwise it searches for scopes instead.
// Don't have a solution for that now.
{
filesystem: {
'notindex.bigb': `= Index
<notindex2>
`,
'notindex2.bigb': `= My notindex2
`
},
convert_dir: true,
assert_bigb: {
'notindex.bigb': `= Index
<notindex2>
`,
}
}
)
assert_lib('bigb output: pluralize fail',
// Was blowing up on pluralize failures. Notably, pluralize is wrong for every -osis suffix,
// common in scientific literature. This is the current buggy behaviour of pluralize:
//
// So for now, if pluralize is wrong, we just abort and do a sane link.
//
//> pluralize('tuberculosis', 1)
// 'tuberculosi'
// > pluralize('tuberculosis', 2)
// 'tuberculoses'
// > pluralize('tuberculoses', 2)
// 'tuberculoses'
// > pluralize('tuberculoses', 1)
// 'tuberculose'
{
filesystem: {
'notindex.bigb': `= Index
\\x[tuberculosis]
\\x[tuberculosis]{p}
\\x[tuberculosis]{magic}
== Tuberculosis
`,
},
convert_dir: true,
assert_bigb: {
'notindex.bigb': `= Index
<tuberculosis>
\\x[tuberculosis]{p}
\\x[tuberculosis]{magic}
== Tuberculosis
`,
}
}
)
assert_lib('bigb output: acronym plural',
// https://github.com/plurals/pluralize/issues/127
{
filesystem: {
'notindex.bigb': `= Notindex
== PC
<PCs>
`,
},
convert_dir: true,
assert_bigb: {
'notindex.bigb': `= Notindex
== PC
<PCs>
`,
}
}
)
assert_lib('bigb output: to file',
{
filesystem: {
'notindex.bigb': `= Index
<path/to/my file>{file}
== path/to/my file
{file}
`,
'path/to/my file': '',
},
convert_dir: true,
assert_bigb: {
'notindex.bigb': `= Index
<path/to/my file>{file}
== path/to/my file
{file}
`,
}
}
)
assert_lib('bigb output: to explicit id with slash',
{
filesystem: {
'notindex.bigb': `= Index
<asdf/qwer>
== Qwer
{id=asdf/qwer}
`,
'path/to/my file': '',
},
convert_dir: true,
assert_bigb: {
'notindex.bigb': `= Index
<asdf/qwer>
== Qwer
{id=asdf/qwer}
`,
}
}
)
assert_lib('bigb output: x do not change capitalization if the first ast is not plaintext',
{
filesystem: {
'notindex.bigb': `= Index
<so 3>
== $SO(3)$
`,
'path/to/my file': '',
},
convert_dir: true,
assert_bigb: {
'notindex.bigb': `= Index
<SO(3)>
== $SO(3)$
`,
}
}
)
assert_lib_error('bigb output: x to undefined does not blow up',
`<asdf>`,
1, 2, undefined,
{
convert_opts: { output_format: ourbigbook.OUTPUT_FORMAT_OURBIGBOOK },
}
)
assert_lib_error('bigb output: undefined tag does not blow up',
`= My header
{tag=Asdf}`,
2, 1, undefined,
{
convert_opts: { output_format: ourbigbook.OUTPUT_FORMAT_OURBIGBOOK },
}
)
assert_lib('bigb output: x convert parent, tag and child IDs to insane magic',
{
filesystem: {
'notindex.bigb': `= Notindex
= My \\i[h2] é \\}
{disambiguate=dis}
{parent=notindex}
= My h3
{child=my-h2-e-dis}
{parent=my-h2-e-dis}
{tag=my-h2-e-dis}
= Myscope
{parent=notindex}
{scope}
= Myscope
{parent=myscope}
= Escape scope
{parent=/myscope}
`,
},
convert_dir: true,
assert_bigb: {
'notindex.bigb': `= Notindex
= My \\i[h2] é \\}
{disambiguate=dis}
{parent=Notindex}
= My h3
{child=My h2 é (dis)}
{parent=My h2 é (dis)}
{tag=My h2 é (dis)}
= Myscope
{parent=Notindex}
{scope}
= Myscope
{parent=Myscope}
= Escape scope
{parent=/Myscope}
`,
}
}
)
assert_lib('bigb output: split_headers',
{
convert_opts: { split_headers: true },
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
Paragraph in notindex.
== Notindex 2
Paragraph in notindex 2.
= Notindex 3
{parent=Notindex 2}
Paragraph in notindex 3.
`,
},
assert_bigb: {
'notindex.bigb': `= Notindex
Paragraph in notindex.
== Notindex 2
Paragraph in notindex 2.
= Notindex 3
{parent=Notindex 2}
Paragraph in notindex 3.
`,
'notindex-split.bigb': `= Notindex
Paragraph in notindex.
`,
'notindex-2.bigb': `= Notindex 2
Paragraph in notindex 2.
`,
// Parent is auto-removed on splits.
'notindex-3.bigb': `= Notindex 3
Paragraph in notindex 3.
`,
}
}
)
// ourbigbook executable tests.
assert_cli(
'input from stdin produces output on stdout simple',
{
stdin: 'aabb',
assert_not_exists: ['out'],
assert_xpath_stdout: ["//x:div[@class='p' and text()='aabb']"],
}
)
assert_cli(
'input from stdin produces output on stdout when in git repository',
{
pre_exec: [['git', ['init']]],
stdin: 'aabb',
assert_not_exists: ['out'],
assert_xpath_stdout: ["//x:div[@class='p' and text()='aabb']"],
}
)
assert_cli(
// Was blowing up on file existence check.
'input from stdin with relative link does not blow up',
{
stdin: '\\a[asdf]',
assert_not_exists: ['out'],
assert_xpath_stdout: [`//x:a[@href='${ourbigbook.RAW_PREFIX}/asdf']`],
filesystem: { 'asdf': '' },
}
)
assert_cli(
'input from stdin ignores ourbigbook.liquid.html',
{
stdin: 'qwer',
assert_xpath_stdout: [`//x:div[@class='p' and text()='qwer']`],
filesystem: {
'ourbigbook.liquid.html': 'asdf'
},
}
)
assert_cli(
'input from file and --stdout produces output on stdout',
{
args: ['--stdout', 'notindex.bigb'],
assert_xpath_stdout: ["//x:div[@class='p' and text()='aabb']"],
filesystem: { 'notindex.bigb': 'aabb' },
}
)
assert_cli(
'input from file produces an output file',
{
args: ['notindex.bigb'],
filesystem: {
'notindex.bigb': `= Notindex\n`,
},
assert_xpath: {
'out/html/notindex.html': [xpath_header(1, 'notindex')],
}
}
)
assert_cli(
'input from two files produces two output files',
{
args: ['notindex.bigb', 'notindex2.bigb'],
filesystem: {
'notindex.bigb': `= Notindex\n`,
'notindex2.bigb': `= Notindex2\n`,
},
assert_xpath: {
'out/html/notindex.html': [xpath_header(1, 'notindex')],
'out/html/notindex2.html': [xpath_header(1, 'notindex2')],
}
}
)
assert_cli(
'--no-render prevents rendering',
{
args: ['--no-render', 'notindex.bigb'],
filesystem: {
'notindex.bigb': `= Notindex\n`,
},
assert_exists: [
// This would be good to assert, but it then fails the test on PostgreSQL
//'out/db.sqlite3'
],
assert_not_exists: [
'out/html/notindex.html'
],
}
)
assert_cli(
'db is checked for duplicates by default',
{
args: ['--no-render', '.'],
filesystem: {
'notindex.bigb': `= Notindex
== Duplicated
`,
'notindex2.bigb': `= Notindex2
== Duplicated
`,
},
assert_exit_status: 1,
}
)
assert_cli(
'--check-db-only exits with error status on failure',
{
pre_exec: [
{ cmd: ['ourbigbook', ['--no-check-db', '--no-render', '.']], },
],
args: ['--check-db-only'],
filesystem: {
'notindex.bigb': `= Notindex
== Duplicated
`,
'notindex2.bigb': `= Notindex2
== Duplicated
`,
},
assert_exit_status: 1,
}
)
const complex_filesystem = {
'README.bigb': `= Index
\\x[notindex][link to notindex]
\\x[h2]{full}
\\x[notindex-h2][link to notindex h2]
\\x[has-split-suffix][link to has split suffix]
\\x[toplevel-scope]
\\x[toplevel-scope/toplevel-scope-h2]
\\x[subdir][link to subdir]
\\x[subdir/index-h2][link to subdir index h2]
\\x[subdir/notindex][link to subdir notindex]
\\x[subdir/notindex-h2][link to subdir notindex h2]
\\x[included-by-index][link to included by index]
$$
\\newcommand{\\mycmd}[0]{hello}
$$
\\OurBigBookExample[[
\\Q[A Ourbigbook example!]
]]
\\Include[included-by-index]
== h2
$$
\\mycmd
$$
\\Include[included-by-h2-in-index]
== h2 2
\\x[h2]{full}
\\x[h4-3-2-1]{full}
=== h3 2 1
\\x[h4-3-2-1]{full}
== h2 3
\\x[h4-3-2-1]{full}
=== h3 3 1
=== h3 3 2
==== h4 3 2 1
== Index scope
{scope}
=== Index scope child
=== Index scope 2
{scope}
== Has split suffix
{splitSuffix}
`,
'notindex.bigb': `= Notindex
\\x[index][link to index]
\\x[h2][link to h2]
== notindex h2
`,
'toplevel-scope.bigb': `= Toplevel scope
{scope}
== Toplevel scope h2
== Nested scope
{scope}
=== Nested scope 2
{scope}
`,
'included-by-index.bigb': `= Included by index
== Included by index h2
`,
'included-by-h2-in-index.bigb': `= Included by h2 in index
== Included by h2 in index h2
`,
'notindex-splitsuffix.bigb': `= Notindex splitsuffix
{splitSuffix=asdf}
`,
'scss.scss': `body { color: red }`,
'ourbigbook.json': `{}\n`,
'subdir/index.bigb': `= Subdir index
\\x[index][link to toplevel]
\\x[h2][link to toplevel subheader]
\\x[has-split-suffix][link to has split suffix]
\\x[notindex][link to subdir notindex]
\\Include[included-by-subdir-index]
== Scope
{scope}
=== h3
\\x[scope][scope/h3 to scope]
\\x[h3][scope/h3 to scope/h3]
== Index h2
`,
'subdir/notindex.bigb': `= Subdir notindex
== Notindex h2
== Notindex scope
{scope}
=== h3
`,
'subdir/included-by-subdir-index.bigb': `= Included by subdir index
== Included by subdir index h2
`,
'subdir/myfile.txt': `Hello world
Goodbye world.
`,
};
assert_cli(
// This is a big catch-all and should likely be split.
'input from directory with ourbigbook.json produces several output files',
{
args: ['--split-headers', '.'],
filesystem: complex_filesystem,
assert_xpath: {
'out/html/h2-2.html': [
// These headers are not children of the current toplevel header.
// Therefore, they do not get a number like "Section 2.".
"//x:div[@class='p']//x:a[@href='index.html#h2' and text()='Section \"h2\"']",
"//x:div[@class='p']//x:a[@href='index.html#h4-3-2-1' and text()='Section \"h4 3 2 1\"']",
],
'out/html/h3-2-1.html': [
// Not a child of the current toplevel either.
"//x:div[@class='p']//x:a[@href='index.html#h4-3-2-1' and text()='Section \"h4 3 2 1\"']",
],
'out/html/h2-3.html': [
// This one is under the current tree, so it shows fully.
"//x:div[@class='p']//x:a[@href='index.html#h4-3-2-1' and text()='Section 2.1. \"h4 3 2 1\"']",
],
'out/html/notindex.html': [
xpath_header(1, 'notindex'),
"//x:div[@class='p']//x:a[@href='index.html' and text()='link to index']",
"//x:div[@class='p']//x:a[@href='index.html#h2' and text()='link to h2']",
],
'out/html/has-split-suffix-split.html': [
xpath_header(1, 'has-split-suffix'),
],
// Custom splitSuffix `-asdf` instead of the default `-split`.
'out/html/index.html': [
xpath_header(1, 'index'),
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='link to notindex']",
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='link to notindex h2']",
"//x:div[@class='p']//x:a[@href='#has-split-suffix' and text()='link to has split suffix']",
"//x:a[@href='subdir.html' and text()='link to subdir']",
"//x:a[@href='subdir.html#index-h2' and text()='link to subdir index h2']",
"//x:a[@href='subdir/notindex.html' and text()='link to subdir notindex']",
"//x:a[@href='subdir/notindex.html#notindex-h2' and text()='link to subdir notindex h2']",
// ToC entries of includes point directly to the separate file, not to the plceholder header.
// e.g. `included-by-index.html` instead of `#included-by-index`.
"//x:a[@href='included-by-index.html' and text()='link to included by index']",
"//*[@id='_toc']//x:a[@href='included-by-index.html' and text()='1. Included by index']",
xpath_header(2, 'included-by-index'),
"//x:blockquote[text()='A Ourbigbook example!']",
xpath_header_split(2, 'index-scope', 'index-scope.html', ourbigbook.SPLIT_MARKER_TEXT),
xpath_header_split(3, 'index-scope/index-scope-2', 'index-scope/index-scope-2.html', ourbigbook.SPLIT_MARKER_TEXT),
],
'out/html/included-by-index.html': [
// Cross input file header.
xpath_header_parent(1, 'included-by-index', 'index.html', 'Index'),
],
'out/html/included-by-index-split.html': [
// Cross input file header on split header.
xpath_header_parent(1, 'included-by-index', 'index.html', 'Index'),
],
'out/html/included-by-h2-in-index.html': [
xpath_header_parent(1, 'included-by-h2-in-index', 'index.html#h2', 'h2'),
],
'out/html/included-by-h2-in-index-split.html': [
xpath_header_parent(1, 'included-by-h2-in-index', 'index.html#h2', 'h2'),
],
'out/html/notindex-splitsuffix-asdf.html': [
],
'out/html/split.html': [
// Full links between split header pages have correct numbering.
"//x:div[@class='p']//x:a[@href='index.html#h2' and text()='Section 2. \"h2\"']",
// OurBigBookExample renders in split header.
"//x:blockquote[text()='A Ourbigbook example!']",
// We have gone back and forth on split vs nosplit here a bit.
// Related: https://github.com/ourbigbook/ourbigbook/issues/146
"//*[@id='_toc']//x:a[@href='index.html#h2' and text()='2. h2']",
// ToC entries of includes always point directly to the separate file.
"//*[@id='_toc']//x:a[@href='included-by-index.html' and text()='1. Included by index']",
// TODO This is more correct with the `1. `. Maybe wait for https://github.com/ourbigbook/ourbigbook/issues/126
// to make sure we don't have to rewrite everything.
//"//*[@id='_toc']//x:a[@href='included-by-index-split.html' and text()='1. Included by index']",
],
'out/html/subdir.html': [
xpath_header(1),
xpath_header_split(1, '', 'subdir/split.html', ourbigbook.SPLIT_MARKER_TEXT),
xpath_header(2, 'index-h2'),
xpath_header_split(2, 'index-h2', 'subdir/index-h2.html', ourbigbook.SPLIT_MARKER_TEXT),
xpath_header(2, 'scope'),
xpath_header_split(2, 'scope', 'subdir/scope.html', ourbigbook.SPLIT_MARKER_TEXT),
xpath_header(3, 'scope/h3'),
xpath_header_split(3, 'scope/h3', 'subdir/scope/h3.html', ourbigbook.SPLIT_MARKER_TEXT),
"//x:a[@href='index.html' and text()='link to toplevel']",
"//x:a[@href='index.html#h2' and text()='link to toplevel subheader']",
"//x:a[@href='subdir/notindex.html' and text()='link to subdir notindex']",
],
'out/html/subdir/split.html': [
xpath_header(1, ''),
xpath_header_split(1, '', '../subdir.html', ourbigbook.NOSPLIT_MARKER_TEXT),
// Check that split suffix works. Should be has-split-suffix-split.html,
// not has-split-suffix.html.
"//x:div[@class='p']//x:a[@href='../index.html#has-split-suffix' and text()='link to has split suffix']",
],
'out/html/subdir/scope/h3.html': [
xpath_header(1, 'h3'),
xpath_header_split(1, 'h3', '../../subdir.html#scope/h3', ourbigbook.NOSPLIT_MARKER_TEXT),
"//x:div[@class='p']//x:a[@href='../../subdir.html#scope' and text()='scope/h3 to scope']",
"//x:div[@class='p']//x:a[@href='../../subdir.html#scope/h3' and text()='scope/h3 to scope/h3']",
],
'out/html/subdir/notindex.html': [
xpath_header(1, 'notindex'),
xpath_header(2, 'notindex-h2'),
xpath_header_split(2, 'notindex-h2', 'notindex-h2.html', ourbigbook.SPLIT_MARKER_TEXT),
],
'out/html/subdir/notindex-scope/h3.html': [
xpath_header(1, 'h3'),
xpath_header_split(1, 'h3', '../notindex.html#notindex-scope/h3', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'out/html/subdir/index-h2.html': [
xpath_header(1, 'index-h2'),
],
'out/html/subdir/notindex-h2.html': [
xpath_header(1, 'notindex-h2'),
],
'out/html/subdir/notindex-split.html': [
xpath_header(1, 'notindex'),
],
'out/html/subdir/notindex-h2.html': [
xpath_header(1, 'notindex-h2'),
],
'out/html/index-scope.html': [
xpath_header_split(1, 'index-scope', 'index.html#index-scope', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'out/html/index-scope/index-scope-child.html': [
// https://github.com/ourbigbook/ourbigbook/issues/159
xpath_header_split(1, 'index-scope-child', '../index.html#index-scope/index-scope-child', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'out/html/index-scope/index-scope-2.html': [
// https://github.com/ourbigbook/ourbigbook/issues/159
xpath_header_split(1, 'index-scope-2', '../index.html#index-scope/index-scope-2', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'out/html/toplevel-scope.html': [
xpath_header_split(2, 'nested-scope', 'toplevel-scope/nested-scope.html', ourbigbook.SPLIT_MARKER_TEXT),
xpath_header_split(3, 'nested-scope/nested-scope-2', 'toplevel-scope/nested-scope/nested-scope-2.html', ourbigbook.SPLIT_MARKER_TEXT),
],
'out/html/toplevel-scope-split.html': [
xpath_header_split(1, 'toplevel-scope', 'toplevel-scope.html', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'out/html/toplevel-scope/toplevel-scope-h2.html': [
xpath_header_split(1, 'toplevel-scope-h2', '../toplevel-scope.html#toplevel-scope-h2', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'out/html/toplevel-scope/nested-scope.html': [
xpath_header_split(1, 'nested-scope', '../toplevel-scope.html#nested-scope', ourbigbook.NOSPLIT_MARKER_TEXT),
],
'out/html/toplevel-scope/nested-scope/nested-scope-2.html': [
// https://github.com/ourbigbook/ourbigbook/issues/159
xpath_header_split(1, 'nested-scope-2', '../../toplevel-scope.html#nested-scope/nested-scope-2', ourbigbook.NOSPLIT_MARKER_TEXT),
],
// Non converted paths.
[`out/html/${ourbigbook.RAW_PREFIX}/scss.css`]: [],
[`out/html/${ourbigbook.RAW_PREFIX}/ourbigbook.json`]: [],
},
assert_not_xpath: {
'out/html/split.html': [
// Included header placeholders are removed from split headers.
xpath_header(1, 'included-by-index'),
xpath_header(2, 'included-by-index'),
],
},
}
)
const publish_filesystem = {
'ourbigbook.json': `{}\n`,
'README.bigb': `= Index
\\x[notindex][link to notindex]
\\x[notindex-h2][link to notindex h2]
== h2
`,
'notindex.bigb': `= Notindex
\\x[index][link to index]
\\x[h2][link to h2]
== notindex h2
`,
'toplevel-scope.bigb': `= Toplevel scope
{scope}
== Toplevel scope h2
`,
'subdir/index.bigb': `= Subdir index
`,
'scss.scss': `body { color: red }`,
'subdir/myfile.txt': `Hello world
Goodbye world.
`,
};
const publish_pre_exec = [
['git', ['init']],
['git', ['add', '.']],
['git', ['commit', '-m', '0']],
['git', ['remote', 'add', 'origin', 'git@github.com:ourbigbook/test.git']],
]
assert_cli(
'publish: --dry-run --split-headers --publish works',
{
args: ['--dry-run', '--split-headers', '--publish', '.'],
filesystem: publish_filesystem,
pre_exec: publish_pre_exec,
assert_exists: [
`out/publish/out/github-pages/${ourbigbook_nodejs.PUBLISH_ASSET_DIST_PREFIX}/ourbigbook.css`,
// Non-converted files are copied over.
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/scss.css`,
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/ourbigbook.json`,
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/subdir/myfile.txt`,
// Directories listings are generated.
`out/publish/out/github-pages/${ourbigbook.DIR_PREFIX}/index.html`,
`out/publish/out/github-pages/${ourbigbook.DIR_PREFIX}/subdir/index.html`,
],
assert_not_exists: [
// logo.svg is not added when web.linkFromStaticHeaderMetaToWeb is not enabled on ourbigbook.json
`out/publish/out/github-pages/_obb/logo.svg`,
],
assert_xpath: {
'out/publish/out/github-pages/index.html': [
"//x:div[@class='p']//x:a[@href='notindex' and text()='link to notindex']",
"//x:div[@class='p']//x:a[@href='notindex#notindex-h2' and text()='link to notindex h2']",
`//x:style[contains(text(),'@import \"${ourbigbook_nodejs.PUBLISH_ASSET_DIST_PREFIX}/ourbigbook.css\"')]`,
],
'out/publish/out/github-pages/notindex.html': [
xpath_header(1, 'notindex'),
"//x:div[@class='p']//x:a[@href='.' and text()='link to index']",
"//x:div[@class='p']//x:a[@href='.#h2' and text()='link to h2']",
],
'out/publish/out/github-pages/toplevel-scope/toplevel-scope-h2.html': [
`//x:style[contains(text(),'@import \"../${ourbigbook_nodejs.PUBLISH_ASSET_DIST_PREFIX}/ourbigbook.css\"')]`,
],
'out/publish/out/github-pages/subdir.html': [
`//x:style[contains(text(),'@import \"${ourbigbook_nodejs.PUBLISH_ASSET_DIST_PREFIX}/ourbigbook.css\"')]`,
],
},
}
)
assert_cli(
'publish: --publish-target local works',
{
args: ['--dry-run', '--split-headers', '--publish', '--publish-target', 'local', '.'],
filesystem: publish_filesystem,
pre_exec: publish_pre_exec,
assert_exists: [
`out/publish/out/local/${ourbigbook_nodejs.PUBLISH_ASSET_DIST_PREFIX}/ourbigbook.css`,
],
assert_xpath: {
'out/publish/out/local/index.html': [
"//x:div[@class='p']//x:a[@href='notindex.html' and text()='link to notindex']",
"//x:div[@class='p']//x:a[@href='notindex.html#notindex-h2' and text()='link to notindex h2']",
`//x:style[contains(text(),'@import \"${ourbigbook_nodejs.PUBLISH_ASSET_DIST_PREFIX}/ourbigbook.css\"')]`,
],
'out/publish/out/local/notindex.html': [
xpath_header(1, 'notindex'),
"//x:div[@class='p']//x:a[@href='index.html' and text()='link to index']",
"//x:div[@class='p']//x:a[@href='index.html#h2' and text()='link to h2']",
],
'out/publish/out/local/toplevel-scope/toplevel-scope-h2.html': [
`//x:style[contains(text(),'@import \"../${ourbigbook_nodejs.PUBLISH_ASSET_DIST_PREFIX}/ourbigbook.css\"')]`,
],
'out/publish/out/local/subdir.html': [
`//x:style[contains(text(),'@import \"${ourbigbook_nodejs.PUBLISH_ASSET_DIST_PREFIX}/ourbigbook.css\"')]`,
],
// Non-converted files are copied over.
[`out/publish/out/local/${ourbigbook.RAW_PREFIX}/scss.css`]: [],
[`out/publish/out/local/${ourbigbook.RAW_PREFIX}/ourbigbook.json`]: [],
[`out/publish/out/local/${ourbigbook.RAW_PREFIX}/subdir/myfile.txt`]: [],
},
}
)
assert_cli(
'json: web.linkFromStaticHeaderMetaToWeb = true with publish',
{
args: ['--dry-run', '--split-headers', '--publish', '.'],
filesystem: {
'ourbigbook.json': `{
"web": {
"linkFromStaticHeaderMetaToWeb": true,
"username": "myusername"
}
}
`,
'README.bigb': `= Index
== h2
{scope}
=== h2 2
`,
},
pre_exec: publish_pre_exec,
assert_exists: [
`out/publish/out/github-pages/_obb/logo.svg`,
],
assert_xpath: {
'out/publish/out/github-pages/index.html': [
"//x:div[contains(@class, \"h \") and @id='index']//x:img[@class='logo' and @src='_obb/logo.svg']",
"//x:div[contains(@class, \"h \") and @id='index']//x:a[@href='https://ourbigbook.com/myusername' and text()=' OurBigBook.com']",
"//x:div[@class='h' and @id='h2']//x:a[@href='https://ourbigbook.com/myusername/h2' and text()=' OurBigBook.com']",
],
'out/publish/out/github-pages/h2/h2-2.html': [
"//x:div[contains(@class, \"h \") and @id='h2-2']//x:img[@class='logo' and @src='../_obb/logo.svg']",
"//x:div[contains(@class, \"h \") and @id='h2-2']//x:a[@href='https://ourbigbook.com/myusername/h2/h2-2' and text()=' OurBigBook.com']",
],
},
}
)
assert_cli(
'json: web.host changes web.linkFromStaticHeaderMetaToWeb host',
{
args: ['.'],
filesystem: {
'ourbigbook.json': `{
"web": {
"linkFromStaticHeaderMetaToWeb": true,
"host": "asdf.com",
"username": "myusername"
}
}
`,
'README.bigb': `= Index
`,
},
pre_exec: publish_pre_exec,
assert_xpath: {
'out/html/index.html': [
"//x:div[contains(@class, \"h \") and @id='index']//x:a[@href='https://asdf.com/myusername' and text()=' asdf.com']",
],
},
}
)
assert_cli(
'json: web.hostCapitalized takes precedence over web.host with web.linkFromStaticHeaderMetaToWeb',
{
args: ['.'],
filesystem: {
'ourbigbook.json': `{
"web": {
"linkFromStaticHeaderMetaToWeb": true,
"host": "asdf.com",
"hostCapitalized": "AsDf.com",
"username": "myusername"
}
}
`,
'README.bigb': `= Index
`,
},
pre_exec: publish_pre_exec,
assert_xpath: {
'out/html/index.html': [
"//x:div[contains(@class, \"h \") and @id='index']//x:a[@href='https://asdf.com/myusername' and text()=' AsDf.com']",
],
},
}
)
assert_cli(
'json: web.linkFromStaticHeaderMetaToWeb = true without publish',
{
args: ['--split-headers', '.'],
filesystem: {
'ourbigbook.json': `{
"web": {
"linkFromStaticHeaderMetaToWeb": true,
"username": "myusername"
}
}
`,
'README.bigb': `= Index
== h2
{scope}
=== h2 2
`,
},
pre_exec: publish_pre_exec,
assert_xpath: {
'out/html/index.html': [
`//x:div[contains(@class, "h ") and @id='index']//x:img[@class='logo' and @src='${ourbigbook_nodejs.LOGO_PATH}']`,
"//x:div[contains(@class, \"h \") and @id='index']//x:a[@href='https://ourbigbook.com/myusername' and text()=' OurBigBook.com']",
`//x:div[@class='h' and @id='h2']//x:img[@class='logo' and @src='${ourbigbook_nodejs.LOGO_PATH}']`,
"//x:div[@class='h' and @id='h2']//x:a[@href='https://ourbigbook.com/myusername/h2' and text()=' OurBigBook.com']",
],
'out/html/h2/h2-2.html': [
`//x:div[contains(@class, "h ") and @id='h2-2']//x:img[@class='logo' and @src='${ourbigbook_nodejs.LOGO_PATH}']`,
"//x:div[contains(@class, \"h \") and @id='h2-2']//x:a[@href='https://ourbigbook.com/myusername/h2/h2-2' and text()=' OurBigBook.com']",
],
},
}
)
assert_cli(
'convert subdirectory only with ourbigbook.json',
{
args: ['subdir'],
filesystem: {
'ourbigbook.json': `{}\n`,
'README.bigb': `= Index`,
'subdir/index.bigb': `= Subdir index`,
'subdir/notindex.bigb': `= Subdir notindex`,
// A Sass file.
'subdir/scss.scss': `body { color: red }`,
// A random non-ourbigbook file.
'subdir/xml.xml': `<?xml version='1.0'?><a/>`,
},
// Place out next to ourbigbook.json which should be the toplevel.
assert_exists: [
'out',
`out/html/${ourbigbook.RAW_PREFIX}/subdir/scss.css`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/xml.xml`,
],
assert_not_exists: [
'out/html/subdir/out',
`out/html/xml.xml`,
`out/html/${ourbigbook.RAW_PREFIX}/scss.css`,
`out/html/${ourbigbook.RAW_PREFIX}/scss.css`,
'out/html/index.html',
],
assert_xpath: {
'out/html/subdir.html': [xpath_header(1)],
'out/html/subdir/notindex.html': [xpath_header(1, 'notindex')],
}
}
)
assert_cli(
'convert subdirectory only without ourbigbook.json',
{
args: ['subdir'],
filesystem: {
'README.bigb': `= Index`,
'subdir/index.bigb': `= Subdir index`,
'subdir/notindex.bigb': `= Subdir notindex`,
'subdir/scss.scss': `body { color: red }`,
'subdir/xml.xml': `<?xml version='1.0'?><a/>`,
},
// Don't know a better place to place out, so just put it int subdir.
assert_exists: [
'out',
`out/html/${ourbigbook.RAW_PREFIX}/subdir/scss.css`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/xml.xml`,
],
assert_not_exists: [
'out/html/index.html',
`out/html/${ourbigbook.RAW_PREFIX}/scss.css`,
'out/html/subdir/out',
'out/html/xml.xml',
],
assert_xpath: {
'out/html/subdir.html': [xpath_header(1, '')],
'out/html/subdir/notindex.html': [xpath_header(1, 'notindex')],
}
}
)
assert_cli(
'convert a subdirectory file only with ourbigbook.json',
{
args: ['subdir/notindex.bigb'],
filesystem: {
'README.bigb': `= Index`,
'subdir/index.bigb': `= Subdir index`,
'subdir/notindex.bigb': `= Subdir notindex`,
'ourbigbook.json': `{}`,
},
// Place out next to ourbigbook.json which should be the toplevel.
assert_exists: ['out'],
assert_not_exists: ['out/html/subdir/out', 'out/html/index.html', 'out/html/subdir.html'],
assert_xpath: {
'out/html/subdir/notindex.html': [xpath_header(1, 'notindex')],
},
}
)
assert_cli(
'convert a subdirectory file only without ourbigbook.json',
{
args: ['subdir/notindex.bigb'],
filesystem: {
'README.bigb': `= Index`,
'subdir/index.bigb': `= Subdir index`,
'subdir/notindex.bigb': `= Subdir notindex`,
},
// Don't know a better place to place out, so just put it int subdir.
assert_exists: ['out'],
assert_not_exists: ['out/html/subdir/out', 'out/html/index.html', 'out/html/subdir.html'],
assert_xpath: {
'out/html/subdir/notindex.html': [xpath_header(1, 'notindex')],
},
}
)
assert_cli(
'convert with --outdir',
{
args: ['--outdir', 'my_outdir', '.'],
filesystem: {
'README.bigb': `= Index`,
'subdir/index.bigb': `= Subdir index`,
'subdir/notindex.bigb': `= Subdir notindex`,
'ourbigbook.json': `{}\n`,
},
assert_exists: [
'my_outdir/out',
`my_outdir/${ourbigbook.RAW_PREFIX}/ourbigbook.json`,
],
assert_not_exists: [
'out',
'index.html',
'subdir.html',
'subdir/notindex.html',
],
assert_xpath: {
'my_outdir/index.html': [xpath_header(1, '')],
'my_outdir/subdir.html': [xpath_header(1, '')],
'my_outdir/subdir/notindex.html': [xpath_header(1, 'notindex')],
}
}
)
assert_cli(
'ourbigbook.tex does not blow up',
{
args: ['README.bigb'],
filesystem: {
'README.bigb': `$$\\mycmd$$`,
'ourbigbook.tex': `\\newcommand{\\mycmd}[0]{hello}`,
},
}
)
assert_cli(
'synonym to outdir generates correct redirct with outdir',
{
args: ['--outdir', 'asdf', '--split-headers', '.'],
filesystem: {
'README.bigb': `= Index
== h2
= My h2 synonym
{c}
{synonym}
`,
},
assert_xpath: {
'asdf/my-h2-synonym.html': [
"//x:script[text()=\"location='index.html#h2'\"]",
],
}
}
)
assert_cli(
// https://github.com/ourbigbook/ourbigbook/issues/114
'synonym to outdir generates correct redirct without outdir',
{
args: ['--split-headers', '.'],
filesystem: {
'README.bigb': `= Index
== h2
= My h2 synonym
{c}
{synonym}
`,
'notindex.bigb': `= Notindex
== Notindex h2
= My notindex h2 synonym
{synonym}
`,
},
assert_xpath: {
'out/html/my-h2-synonym.html': [
"//x:script[text()=\"location='index.html#h2'\"]",
],
'out/html/my-notindex-h2-synonym.html': [
"//x:script[text()=\"location='notindex.html#notindex-h2'\"]",
],
}
}
)
assert_cli(
'--generate min followed by conversion does not blow up',
{
args: ['.'],
pre_exec: [
['ourbigbook', ['--generate', 'min']],
],
}
)
assert_cli(
'--generate min followed by publish does not blow up',
{
args: ['--publish', '--dry-run'],
pre_exec: [
['ourbigbook', ['--generate', 'subdir']],
].concat(MAKE_GIT_REPO_PRE_EXEC),
}
)
assert_cli(
'--generate min in subdir does not alter toplevel',
{
args: ['.'],
filesystem: {
'ourbigbook.json': `{}`
},
cwd: 'subdir',
pre_exec: [
['ourbigbook', ['--generate', 'min']],
],
assert_exists: [
'subdir/README.bigb',
],
assert_not_exists: [
'README.bigb',
],
}
)
assert_cli(
'--generate default followed by conversion does not blow up',
{
args: ['.'],
pre_exec: [
['ourbigbook', ['--generate', 'default']],
['git', ['init']],
['git', ['add', '.']],
['git', ['commit', '-m', '0']],
],
}
)
assert_cli(
'--generate subdir followed by conversion does not blow up',
{
args: ['docs'],
pre_exec: [
['ourbigbook', ['--generate', 'subdir']],
],
}
)
assert_cli(
'--generate min followed by publish conversion does not blow up',
{
args: ['--dry-run', '--publish'],
pre_exec: [
['ourbigbook', ['--generate', 'min']],
].concat(MAKE_GIT_REPO_PRE_EXEC),
}
)
assert_cli(
'--generate default followed by publish conversion does not blow up',
{
args: ['--dry-run', '--publish'],
pre_exec: [
['ourbigbook', ['--generate', 'default']],
].concat(MAKE_GIT_REPO_PRE_EXEC),
}
)
assert_cli(
'--generate subdir followed by publish conversion does not blow up',
{
args: ['--dry-run', '--publish'],
cwd: 'docs',
pre_exec: [
['ourbigbook', ['--generate', 'subdir']],
].concat(MAKE_GIT_REPO_PRE_EXEC),
}
)
assert_cli(
'--embed-resources actually embeds resources',
{
args: ['--embed-resources', '.'],
filesystem: {
'README.bigb': `= Index
`,
},
assert_xpath: {
'out/html/index.html': [
// The start of a minified CSS rule from ourbigbook.scss.
"//x:style[contains(text(),'.ourbigbook{')]",
],
},
assert_not_xpath: {
'out/html/index.html': [
// The way that we import other sheets.
"//x:style[contains(text(),'@import ')]",
],
}
}
)
assert_cli(
'reference to subdir with --embed-includes',
{
args: ['--embed-includes', 'README.bigb'],
filesystem: {
'README.bigb': `= Index
\\x[subdir]
\\x[subdir/h2]
\\x[subdir/notindex]
\\x[subdir/notindex-h2]
\\Include[subdir]
\\Include[subdir/notindex]
`,
'subdir/index.bigb': `= Subdir
== h2
`,
'subdir/notindex.bigb': `= Notindex
== Notindex h2
`,
},
}
)
// executable cwd tests
assert_cli(
"cwd outside project directory given by ourbigbook.json",
{
args: ['myproject'],
filesystem: {
'myproject/README.bigb': `= Index
\\x[not-readme]
\\x[subdir]
\\Include[not-readme]
\\Include[subdir]
\\Include[subdir/notindex]
`,
'myproject/not-readme.bigb': `= Not readme
`,
'myproject/scss.scss': `body { color: red }`,
'myproject/ourbigbook.json': `{}
`,
'myproject/subdir/index.bigb': `= Subdir
`,
'myproject/subdir/notindex.bigb': `= Subdir Notindex
`,
},
assert_exists: [
'myproject/out/html',
`myproject/out/html/${ourbigbook.RAW_PREFIX}/scss.css`,
`myproject/out/html/${ourbigbook.RAW_PREFIX}/ourbigbook.json`,
],
assert_xpath: {
'myproject/out/html/index.html': [
xpath_header(1, ''),
],
'myproject/out/html/subdir.html': [
xpath_header(1, ''),
]
}
}
)
assert_cli(
"if there is no ourbigbook.json and the input is not under cwd then the project dir is the input dir",
{
args: [path.join('..', 'myproject')],
cwd: 'notmyproject',
filesystem: {
'myproject/README.bigb': `= Index
\\x[not-readme]
\\x[subdir]
\\Include[not-readme]
\\Include[subdir]
\\Include[subdir/notindex]
`,
'myproject/not-readme.bigb': `= Not readme
`,
'myproject/scss.scss': `body { color: red }`,
'myproject/subdir/index.bigb': `= Subdir
`,
'myproject/subdir/notindex.bigb': `= Subdir Notindex
`,
},
assert_exists: [
'myproject/out',
`myproject/out/html/${ourbigbook.RAW_PREFIX}/scss.css`,
],
assert_xpath: {
'myproject/out/html/index.html': [
xpath_header(1, ''),
],
'myproject/out/html/subdir.html': [
xpath_header(1, ''),
]
}
}
)
assert_cli(
'template: root_relpath and root_path in ourbigbook.liquid.html work',
{
args: ['-S', '.'],
filesystem: {
'README.bigb': `= Index
== h2
`,
'notindex.bigb': `= Notindex
== Notindex h2
{scope}
=== h3
`,
'ourbigbook.json': `{}
`,
'ourbigbook.liquid.html': `<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
</head>
<body>
<a id="root-relpath" href="{{ root_relpath }}">Root relpath</a>
<a id="root-page" href="{{ root_page }}">Root page</a>
{{ post_body }}
</body>
</html>
`
},
assert_xpath: {
'out/html/index.html': [
"//x:a[@id='root-relpath' and @href='']",
"//x:a[@id='root-page' and @href='']",
],
'out/html/split.html': [
"//x:a[@id='root-relpath' and @href='']",
"//x:a[@id='root-page' and @href='index.html']",
],
'out/html/h2.html': [
"//x:a[@id='root-relpath' and @href='']",
"//x:a[@id='root-page' and @href='index.html']",
],
'out/html/notindex.html': [
"//x:a[@id='root-relpath' and @href='']",
"//x:a[@id='root-page' and @href='index.html']",
],
'out/html/notindex-split.html': [
"//x:a[@id='root-relpath' and @href='']",
"//x:a[@id='root-page' and @href='index.html']",
],
'out/html/notindex-h2.html': [
"//x:a[@id='root-relpath' and @href='']",
"//x:a[@id='root-page' and @href='index.html']",
],
'out/html/notindex-h2/h3.html': [
"//x:a[@id='root-relpath' and @href='../']",
"//x:a[@id='root-page' and @href='../index.html']",
],
}
}
)
assert_cli(
'template: is_index_article',
{
args: ['-S', '.'],
filesystem: {
'README.bigb': `= Index
== h2
`,
'notindex.bigb': `= Notindex
== Notindex h2
{scope}
=== h3
`,
'subdir/index.bigb': `= Subdir
== h2
`,
'ourbigbook.json': `{}
`,
'ourbigbook.liquid.html': `<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
</head>
<body>
{% if is_index_article %}<div id="is-index-article"></div>{% endif %}
</body>
</html>
`
},
assert_xpath: {
'out/html/index.html': [
"//x:div[@id='is-index-article']",
],
'out/html/split.html': [
"//x:div[@id='is-index-article']",
],
},
assert_not_xpath: {
'out/html/h2.html': [
"//x:div[@id='is-index-article']",
],
'out/html/notindex.html': [
"//x:div[@id='is-index-article']",
],
'out/html/notindex-split.html': [
"//x:div[@id='is-index-article']",
],
'out/html/notindex-h2.html': [
"//x:div[@id='is-index-article']",
],
'out/html/notindex-h2/h3.html': [
"//x:div[@id='is-index-article']",
],
'out/html/subdir.html': [
"//x:div[@id='is-index-article']",
],
'out/html/subdir/h2.html': [
"//x:div[@id='is-index-article']",
],
},
}
)
assert_cli(
'template: root_relpath and root_page work from subdirs',
{
args: ['-S', '.'],
filesystem: {
'subdir/notindex.bigb': `= Notindex
`,
'ourbigbook.json': `{}
`,
'ourbigbook.liquid.html': `<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
</head>
<body>
<a id="root-relpath" href="{{ root_relpath }}">Root relpath</a>
<a id="root-page" href="{{ root_page }}">Root page</a>
</body>
</html>
`
},
assert_xpath: {
'out/html/subdir/notindex.html': [
"//x:a[@id='root-relpath' and @href='../']",
"//x:a[@id='root-page' and @href='../index.html']",
],
'out/html/subdir/notindex-split.html': [
"//x:a[@id='root-relpath' and @href='../']",
"//x:a[@id='root-page' and @href='../index.html']",
],
}
}
)
assert_cli(
'template: a custom template can be selected from ourbigbook.json',
{
args: ['.'],
filesystem: {
'subdir/notindex.bigb': `= Notindex
`,
'ourbigbook.json': `{
"template": "custom.liquid.html"
}
`,
'custom.liquid.html': `<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
</head>
<body>
<p>asdf</p>
</body>
</html>
`
},
assert_xpath: {
'out/html/subdir/notindex.html': [
"//x:p[text()='asdf']",
],
}
}
)
assert_cli(
'template: null ignores template file',
{
args: ['.'],
filesystem: {
'notindex.bigb': `= Notindex
asdf
`,
'ourbigbook.json': `{
"template": null
}
`,
'ourbigbook.liquid.html': `asdf`
},
assert_xpath: {
'out/html/notindex.html': [
"//x:div[@class='p' and text()='asdf']",
],
}
}
)
assert_cli(
"multiple incoming child and parent links don't blow up",
{
args: ['.'],
filesystem: {
'README.bigb': `= Index
\\x[notindex]{child}
\\x[notindex]{child}
`,
'notindex.bigb': `= Notindex
\\x[index]{parent}
\\x[index]{parent}
`,
},
assert_xpath: {
'out/html/index.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='tagged']//x:a[@href='notindex.html']`,
],
},
}
)
assert_cli(
'ourbigbook.json: outputOutOfTree=true',
{
args: ['-S', '.'],
filesystem: {
'README.bigb': `= Index
== h2
`,
'notindex.bigb': `= Notindex
== Notindex h2
`,
'ourbigbook.json': `{
"outputOutOfTree": true
}
`,
},
assert_exists: [
'out/html/index.html',
'out/html/split.html',
'out/html/h2.html',
'out/html/notindex.html',
'out/html/notindex-h2.html',
],
assert_exists_sqlite: [
'out/db.sqlite3',
],
assert_not_exists: [
'index.html',
'split.html',
'h2.html',
'notindex.html',
'notindex-h2.html',
'out/html/out',
]
}
)
assert_cli(
'ourbigbook.json: outputOutOfTree=false',
{
args: ['-S', '.'],
filesystem: {
'README.bigb': `= Index
== h2
`,
'notindex.bigb': `= Notindex
== Notindex h2
`,
'ourbigbook.json': `{
"outputOutOfTree": false
}
`,
},
assert_exists: [
'index.html',
'split.html',
'h2.html',
'notindex.html',
'notindex-h2.html',
],
assert_exists_sqlite: [
'out/db.sqlite3',
],
assert_not_exists: [
'out/index.html',
'out/split.html',
'out/h2.html',
'out/notindex.html',
'out/notindex-h2.html',
'out/html/out',
]
}
)
assert_cli(
'IDs are removed from the database after you removed them from the source file and convert the file',
{
args: ['notindex.bigb'],
filesystem: {
'README.bigb': `= Index
== h2
`,
'notindex.bigb': `= Notindex
== h2
`,
},
pre_exec: [
['ourbigbook', ['README.bigb']],
// Remove h2 from README.bigb
{
filesystem_update: {
'README.bigb': `= Index
`,
}
},
['ourbigbook', ['README.bigb']],
],
}
)
assert_cli(
'IDs are removed from the database after you removed them from the source file and convert the directory one way',
{
args: ['.'],
filesystem: {
'README.bigb': `= Index
== h2
`,
'notindex.bigb': `= Notindex
== h2
`,
},
pre_exec: [
['ourbigbook', ['README.bigb']],
// Remove h2 from README.bigb
{
filesystem_update: {
'README.bigb': `= Index
`,
}
},
],
}
)
assert_cli(
'IDs are removed from the database after you removed them from the source file and convert the directory reverse',
{
args: ['.'],
filesystem: {
'README.bigb': `= Index
== h2
`,
'notindex.bigb': `= Notindex
== h2
`,
},
pre_exec: [
['ourbigbook', ['notindex.bigb']],
// Remove h2 from README.bigb
{
filesystem_update: {
'notindex.bigb': `= Index
`,
}
},
],
}
)
assert_cli(
'IDs are removed from the database after you delete the source file they were present in and convert the directory',
{
args: ['.'],
filesystem: {
'README.bigb': `= Index
`,
'notindex.bigb': `= Notindex
== h2
`,
},
pre_exec: [
['ourbigbook', ['.']],
{
filesystem_update: {
'README.bigb': `= Index
== h2
`,
'notindex.bigb': null,
}
},
],
}
)
assert_cli(
'when invoking with a single file timestamps are automatically ignored and render is forced',
{
args: ['notindex.bigb'],
assert_xpath: {
'out/html/notindex.html': [
`//x:a[@href='index.html#h2' and text()='h2 hacked']`,
],
},
filesystem: {
'README.bigb': `= Index
== h2
`,
'notindex.bigb': `= Notindex
\\x[h2]
`,
},
pre_exec: [
['ourbigbook', ['.']],
{
filesystem_update: {
'README.bigb': `= Index
== h2 hacked
{id=h2}
`,
}
},
['ourbigbook', ['README.bigb']],
],
}
)
assert_cli(
"toplevel index file without a header produces output to index.html",
{
args: ['README.bigb'],
filesystem: {
'README.bigb': `asdf
`,
},
assert_xpath: {
'out/html/index.html': [
"//x:div[@class='p' and text()='asdf']",
],
},
}
)
assert_cli('cross file ancestors work on single file conversions at toplevel',
{
// After we pre-convert everything, we convert just one file to ensure that the ancestors are coming
// purely from the database, and not from a cache shared across several input files.
args: ['notindex3.bigb'],
filesystem: {
'index.bigb': `= Index
\\Include[notindex]
`,
'notindex.bigb': `= Notindex
\\Include[notindex2]
`,
'notindex2.bigb': `= Notindex 2
\\Include[notindex3]
`,
'notindex3.bigb': `= Notindex 2
`
},
pre_exec: [
// First we pre-convert everything.
['ourbigbook', ['.']],
],
assert_xpath: {
'out/html/notindex.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html']`,
],
'out/html/notindex2.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='notindex.html']`,
],
'out/html/notindex3.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='index.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='notindex.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='notindex2.html']`,
],
},
assert_not_xpath: {
'out/html/index.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']`,
],
},
}
)
assert_cli('cross file ancestors work on single file conversions in subdir',
{
// After we pre-convert everything, we convert just one file to ensure that the ancestors are coming
// purely from the database, and not from a cache shared across several input files.
args: ['subdir/notindex3.bigb'],
filesystem: {
'subdir/index.bigb': `= Index
\\Include[notindex]
`,
'subdir/notindex.bigb': `= Notindex
\\Include[notindex2]
`,
'subdir/notindex2.bigb': `= Notindex 2
\\Include[notindex3]
`,
'subdir/notindex3.bigb': `= Notindex 2
`
},
pre_exec: [
// First we pre-convert everything.
['ourbigbook', ['.']],
],
assert_xpath: {
'out/html/subdir/notindex.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='../subdir.html']`,
],
'out/html/subdir/notindex2.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='../subdir.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='notindex.html']`,
],
'out/html/subdir/notindex3.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='../subdir.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='notindex.html']`,
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']//x:a[@href='notindex2.html']`,
],
},
assert_not_xpath: {
'out/html/subdir.html': [
`//x:ol[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='ancestors']`,
],
},
}
)
assert_cli(
// See also corresponding lib: test.
'incoming links: cross reference incoming links and other children with magic',
{
args: ['-S', '.'],
filesystem: {
'README.bigb': `= Index
== Dog
== Dogs
== Cat
`,
'notindex.bigb': `= Notindex
== To dog
<dog>
== To dogs
<dogs>
`,
'subdir/notindex.bigb': `= Notindex
<cats>
== To dog
<dog>
== To dogs
<dogs>
== Cat
`,
},
assert_xpath: {
'out/html/dog.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html#to-dog']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='subdir/notindex.html#to-dog']`,
],
'out/html/dogs.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html#to-dogs']`,
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='subdir/notindex.html#to-dogs']`,
],
'out/html/subdir/cat.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html']`,
],
},
assert_not_xpath: {
'out/html/dog.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html#to-dogs']`,
],
'out/html/dogs.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']//x:a[@href='notindex.html#to-dog']`,
],
'out/html/cat.html': [
`//x:ul[@${ourbigbook.Macro.TEST_DATA_HTML_PROP}='incoming-links']`,
],
},
}
)
// JSON
// ourbigbook.json
assert_cli('ourbigbook.json redirects',
{
args: ['.'],
filesystem: {
'ourbigbook.json': `{
"redirects": [
["from", "tourl"],
["from2", "https://tourl.com"]
]
}
`,
},
assert_xpath: {
'out/html/from.html': [
"//x:script[text()=\"location='tourl.html'\"]",
],
'out/html/from2.html': [
// .html not added because it is an absolute URL.
"//x:script[text()=\"location='https://tourl.com'\"]",
],
},
}
)
assert_cli('toplevel scope gets removed on table of contents of included headers',
{
args: ['--split-headers', '.'],
filesystem: {
'index.bigb': `= Index
\\Include[notindex]
`,
'notindex.bigb': `= Notindex
{scope}
== Notindex h2
`,
},
assert_xpath: {
'out/html/index.html': [
"//*[@id='_toc']//x:a[@href='notindex.html' and text()='1. Notindex']",
"//*[@id='_toc']//x:a[@href='notindex.html#notindex-h2' and text()='1.1. Notindex h2']",
],
'out/html/split.html': [
"//*[@id='_toc']//x:a[@href='notindex.html' and text()='1. Notindex']",
"//*[@id='_toc']//x:a[@href='notindex.html#notindex-h2' and text()='1.1. Notindex h2']",
],
},
},
)
assert_cli('id conflict with id on another file simple',
{
args: ['.'],
filesystem: {
'index.bigb': `= index
== notindex h2
`,
'notindex.bigb': `= notindex
== notindex h2
`,
},
assert_exit_status: 1,
}
)
assert_cli(
// https://github.com/ourbigbook/ourbigbook/issues/241
'fixing a header parent bug on a file in the include chain does not blow up afterwards',
{
args: ['.'],
filesystem: {
'README.bigb': `= Index
\\Include[notindex]
`,
'notindex.bigb': `= Notindex
= h2
{parent=notindex}
= h3
{parent=notindex}
= h4
{parent=h2}
`,
},
pre_exec: [
{
cmd: ['ourbigbook', ['.']],
status: 1,
},
{
filesystem_update: {
'notindex.bigb': `= Notindex
= h2
{parent=notindex}
= h3
{parent=notindex}
= h4
{parent=h3}
`,
}
},
],
}
)
assert_cli(
// This is a bit annoying to test from _lib because ourbigbook CLI
// has to pass several variables for it to work.
'link: media-provider github local path with outputOutOfTree',
{
args: ['myproj'],
filesystem: {
'myproj/README.bigb': `\\Image[myimg.png]{provider=github}
`,
'myproj/ourbigbook.json': `{
"media-providers": {
"github": {
"path": "../myproj-media",
"remote": "cirosantilli/myproj-media"
}
}
}
`,
'myproj-media/myimg.png': 'a',
},
assert_xpath: {
'myproj/out/html/index.html': [
// Two .. to get out from under out/html, and one from the media-providers ../myproj-media.
"//x:a[@href='../../../myproj-media/myimg.png']//x:img[@src='../../../myproj-media/myimg.png']",
],
},
}
)
assert_cli(
// This is a bit annoying to test from _lib because ourbigbook CLI
// has to pass several variables for it to work.
'link: media-provider github local path with outputOutOfTree=false',
{
args: ['myproj'],
filesystem: {
'myproj/README.bigb': `\\Image[myimg.png]{provider=github}
`,
'myproj/ourbigbook.json': `{
"media-providers": {
"github": {
"path": "../myproj-media",
"remote": "cirosantilli/myproj-media"
}
},
"outputOutOfTree": false
}
`,
'myproj-media/myimg.png': 'a',
},
assert_xpath: {
'myproj/index.html': [
"//x:a[@href='../myproj-media/myimg.png']//x:img[@src='../myproj-media/myimg.png']",
],
},
}
)
assert_cli(
'link: media-provider github local path is not used when publishing',
{
args: ['--dry-run', '--publish'],
cwd: 'myproj',
pre_exec: MAKE_GIT_REPO_PRE_EXEC,
filesystem: {
'myproj/README.bigb': `\\Image[myimg.png]{provider=github}
`,
'myproj/ourbigbook.json': `{
"media-providers": {
"github": {
"path": "../myproj-media",
"remote": "cirosantilli/myproj-media"
}
}
}
`,
'myproj-media/myimg.png': 'a',
},
assert_xpath: {
'myproj/out/publish/out/github-pages/index.html': [
"//x:a[@href='https://raw.githubusercontent.com/cirosantilli/myproj-media/master/myimg.png']//x:img[@src='https://raw.githubusercontent.com/cirosantilli/myproj-media/master/myimg.png']",
],
},
}
)
assert_cli(
'timestamps are tracked separately for different --output-format',
{
args: ['--output-format', 'bigb', '.'],
filesystem: {
'notindex.bigb': `Hello \\i[world]!
`,
'ourbigbook.json': `{
}
`,
},
pre_exec: [
{
cmd: ['ourbigbook', ['--output-format', 'html', '.']],
},
{
cmd: ['ourbigbook', ['--output-format', 'bigb', '.']],
},
{
filesystem_update: {
'notindex.bigb': `Hello \\i[world2]!
`,
}
},
{
cmd: ['ourbigbook', ['--output-format', 'html', '.']],
},
],
assert_xpath: {
'out/html/notindex.html': [
"//x:i[text()='world2']",
],
},
assert_bigb: {
'out/bigb/notindex.bigb': `Hello \\i[world2]!
`,
},
}
)
assert_cli('bigb output: synonym with split_headers does not produce redirect files',
{
args: ['--split-headers', '--output-format', 'bigb', '.'],
convert_opts: { split_headers: true },
convert_dir: true,
filesystem: {
'notindex.bigb': `= Notindex
== Notindex 2
= Notindex 2 2
{synonym}
`,
},
// Just for sanity, not the actual test.
assert_bigb: {
'out/bigb/notindex-split.bigb': `= Notindex
`,
'out/bigb/notindex-2.bigb': `= Notindex 2
= Notindex 2 2
{synonym}
`,
},
assert_not_exists: [
'out/bigb/notindex-2-2.bigb',
// The actual test.
'out/bigb/notindex-2-2.html',
'out/html/notindex-2-2.bigb',
'out/html/notindex-2-2.html',
],
}
)
assert_cli(
'math: builtin math defines work',
{
stdin: `$$
\\dv{x^2}{x}
$$
`,
}
)
assert_cli(
'raw: bigb source files are copied into raw',
{
args: ['.'],
filesystem: {
'README.bigb': ``,
'notreadme.bigb': ``,
'subdir/README.bigb': ``,
'subdir/notreadme.bigb': ``,
'main.scss': ``,
},
assert_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/README.bigb`,
`out/html/${ourbigbook.RAW_PREFIX}/notreadme.bigb`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/README.bigb`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/notreadme.bigb`,
// Also the source of other converted formats like SCSS.
`out/html/${ourbigbook.RAW_PREFIX}/main.scss`,
]
}
)
assert_cli(
// Due to https://docs.github.com/todo/1 should be 1 triple conversion
// stopped failing. But it should fail.
'x: to undefined ID fails each time despite timestamp skip',
{
args: ['.'],
assert_exit_status: 1,
filesystem: {
'README.bigb': `= Index
<asdf>
`,
},
pre_exec: [
{
cmd: ['ourbigbook', ['.']],
status: 1,
},
{
cmd: ['ourbigbook', ['.']],
status: 1,
},
],
}
)
assert_cli(
'raw: directory listings simple',
{
args: ['-S', '.'],
filesystem: {
'README.bigb': `= Index
\\a[.][link to root]
\\a[subdir][link to subdir]
\\a[subdir/subdir2][link to subdir2]
\\a[index.html][index to index.html]
\\a[_index.html][index to _index.html]
\\a[subdir/index.html][index to subdir/index.html]
== subdir
{file}
== subdir/subdir2
{file}
`,
'subdir/index.bigb': `= Subdir
\\a[..][link to root]
\\a[.][link to subdir]
\\a[subdir2][link to subdir2]
`,
'myfile.txt': `myfile.txt line1
myfile.txt line2
`,
// An image and video to make sure the image handling is taken care of on file autogen. Yes there was a bug.
'myfile-autogen.png': `aaa`,
'myfile-autogen.mp4': `aaa`,
// File and dir autogen escaping of magic OBB characters. Everything breaks everything.
// Directory.
'[/hello.txt': `aaa`,
// File.
'[.txt': `aaa`,
'index.html': '',
'_index.html': '',
'subdir/myfile-subdir.txt': `myfile-subdir.txt line1
myfile-subdir.txt line2
`,
'subdir/index.html': '',
'subdir/subdir2/index.bigb': `= Subdir2
\\a[../..][link to root]
\\a[..][link to subdir]
\\a[.][link to subdir2]
`,
'subdir/subdir2/myfile-subdir2.txt': `ab`,
'.git/myfile-git.txt': `ab`,
},
assert_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/myfile.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/myfile-autogen.png`,
`out/html/${ourbigbook.RAW_PREFIX}/myfile-autogen.mp4`,
`out/html/${ourbigbook.RAW_PREFIX}/index.html`,
`out/html/${ourbigbook.RAW_PREFIX}/_index.html`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/index.html`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/myfile-subdir.txt`,
// Auto-generated {file} by ourbigbook CLI.
`out/html/${ourbigbook.FILE_PREFIX}/myfile.txt.html`,
`out/html/${ourbigbook.FILE_PREFIX}/index.html.html`,
`out/html/${ourbigbook.FILE_PREFIX}/_index.html.html`,
`out/html/${ourbigbook.FILE_PREFIX}/subdir/index.html.html`,
`out/html/${ourbigbook.FILE_PREFIX}/subdir/myfile-subdir.txt.html`,
],
assert_not_exists: [
// Ignored directories are not listed.
`out/html/${ourbigbook.RAW_PREFIX}/.git/index.html`,
],
assert_xpath: {
[`out/html/index.html`]: [
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir/index.html' and text()='subdir' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/subdir__subdir']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir/subdir2/index.html' and text()='subdir2' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/subdir/subdir2__subdir/subdir2']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/index.html' and text()='link to root']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir/index.html' and text()='link to subdir']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir/subdir2/index.html' and text()='link to subdir2']`,
`//x:a[@href='${ourbigbook.RAW_PREFIX}/index.html' and text()='index to index.html']`,
`//x:a[@href='${ourbigbook.RAW_PREFIX}/_index.html' and text()='index to _index.html']`,
`//x:a[@href='${ourbigbook.RAW_PREFIX}/subdir/index.html' and text()='index to subdir/index.html']`,
],
[`out/html/subdir.html`]: [
`//x:a[@href='${ourbigbook.DIR_PREFIX}/index.html' and text()='link to root']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir/index.html' and text()='link to subdir']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir/subdir2/index.html' and text()='link to subdir2']`,
],
[`out/html/subdir/subdir2.html`]: [
`//x:a[@href='../${ourbigbook.DIR_PREFIX}/index.html' and text()='link to root']`,
`//x:a[@href='../${ourbigbook.DIR_PREFIX}/subdir/index.html' and text()='link to subdir']`,
`//x:a[@href='../${ourbigbook.DIR_PREFIX}/subdir/subdir2/index.html' and text()='link to subdir2']`,
],
[`out/html/${ourbigbook.DIR_PREFIX}/index.html`]: [
`//x:a[@href='../${ourbigbook.FILE_PREFIX}/myfile.txt.html' and text()='myfile.txt']`,
`//x:a[@href='../${ourbigbook.FILE_PREFIX}/README.bigb.html' and text()='README.bigb']`,
`//x:a[@href='../${ourbigbook.FILE_PREFIX}/index.html.html' and text()='index.html']`,
`//x:a[@href='../${ourbigbook.FILE_PREFIX}/_index.html.html' and text()='_index.html']`,
`//x:a[@href='subdir/index.html' and text()='subdir/']`,
],
[`out/html/${ourbigbook.DIR_PREFIX}/subdir/index.html`]: [
`//x:a[@href='../../${ourbigbook.FILE_PREFIX}/subdir/myfile-subdir.txt.html' and text()='myfile-subdir.txt']`,
`//x:a[@href='../../${ourbigbook.FILE_PREFIX}/subdir/index.html.html' and text()='index.html']`,
`//x:a[@href='subdir2/index.html' and text()='subdir2/']`,
`//x:a[@href='../index.html' and text()='${ourbigbook.FILE_ROOT_PLACEHOLDER}']`,
],
[`out/html/${ourbigbook.DIR_PREFIX}/subdir/subdir2/index.html`]: [
`//x:a[@href='../../index.html' and text()='${ourbigbook.FILE_ROOT_PLACEHOLDER}']`,
`//x:a[@href='../index.html' and text()='subdir']`,
],
// Auto-generated {file} by ourbigbook CLI.
// It feels natural to slot testing for that here.
[`out/html/${ourbigbook.FILE_PREFIX}/myfile.txt.html`]: [
// We actually get the full path always on the title of a {file} header.
"//x:h1//x:a[text()='myfile.txt']",
"//x:code[starts-with(text(), 'myfile.txt line1')]",
`//x:a[@href='../${ourbigbook.DIR_PREFIX}/index.html' and text()='${ourbigbook.FILE_ROOT_PLACEHOLDER}']`,
`//x:a[@href='../${ourbigbook.RAW_PREFIX}/myfile.txt' and text()='myfile.txt' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/myfile.txt__myfile.txt']`,
],
[`out/html/${ourbigbook.FILE_PREFIX}/subdir/myfile-subdir.txt.html`]: [
"//x:h1//x:a[text()='subdir/myfile-subdir.txt']",
"//x:code[starts-with(text(), 'myfile-subdir.txt line1')]",
`//x:a[@href='../../${ourbigbook.DIR_PREFIX}/index.html' and text()='${ourbigbook.FILE_ROOT_PLACEHOLDER}']`,
`//x:a[@href='../../${ourbigbook.DIR_PREFIX}/subdir/index.html' and text()='subdir' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/subdir/myfile-subdir.txt__subdir']`,
`//x:a[@href='../../${ourbigbook.RAW_PREFIX}/subdir/myfile-subdir.txt' and text()='myfile-subdir.txt' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/subdir/myfile-subdir.txt__subdir/myfile-subdir.txt']`,
],
},
assert_not_xpath: {
[`out/html/${ourbigbook.DIR_PREFIX}/index.html`]: [
// ../ not added to root listing.
`//x:a[text()='${ourbigbook.FILE_ROOT_PLACEHOLDER}']`,
// Ignored files don't show on listing.
"//x:a[text()='.git']",
"//x:a[text()='.git/']",
],
},
}
)
assert_cli(
'raw: directory listings without .html',
{
args: ['-S', '.'],
filesystem: {
'ourbigbook.json': `{
"htmlXExtension": false
}`,
'README.bigb': `= Index
\\a[.][link to root]
\\a[subdir][link to subdir]
\\a[subdir/subdir2][link to subdir2]
\\a[index.html][index to index.html]
\\a[_index.html][index to _index.html]
\\a[subdir/index.html][index to subdir/index.html]
== subdir
{file}
== subdir/subdir2
{file}
`,
'subdir/index.bigb': `= Subdir
\\a[..][link to root]
\\a[.][link to subdir]
\\a[subdir2][link to subdir2]
`,
'myfile.txt': `ab`,
'index.html': '',
'_index.html': '',
'subdir/myfile-subdir.txt': `ab`,
'subdir/index.html': '',
'subdir/subdir2/index.bigb': `= Subdir2
\\a[../..][link to root]
\\a[..][link to subdir]
\\a[.][link to subdir2]
`,
'subdir/subdir2/myfile-subdir2.txt': `ab`,
'.git/myfile-git.txt': `ab`,
},
assert_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/myfile.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/index.html`,
`out/html/${ourbigbook.RAW_PREFIX}/_index.html`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/index.html`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/myfile-subdir.txt`,
`out/html/${ourbigbook.FILE_PREFIX}/myfile.txt.html`,
`out/html/${ourbigbook.FILE_PREFIX}/index.html.html`,
`out/html/${ourbigbook.FILE_PREFIX}/_index.html.html`,
`out/html/${ourbigbook.FILE_PREFIX}/subdir/index.html.html`,
`out/html/${ourbigbook.FILE_PREFIX}/subdir/myfile-subdir.txt.html`,
],
assert_not_exists: [
// Ignored directories are not listed.
`out/html/${ourbigbook.RAW_PREFIX}/.git/index.html`,
],
assert_xpath: {
[`out/html/index.html`]: [
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir' and text()='subdir' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/subdir__subdir']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir/subdir2' and text()='subdir2' and @${ourbigbook.Macro.TEST_DATA_HTML_PROP}='${ourbigbook.FILE_PREFIX}/subdir/subdir2__subdir/subdir2']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}' and text()='link to root']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir' and text()='link to subdir']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir/subdir2' and text()='link to subdir2']`,
`//x:a[@href='${ourbigbook.RAW_PREFIX}/index.html' and text()='index to index.html']`,
`//x:a[@href='${ourbigbook.RAW_PREFIX}/_index.html' and text()='index to _index.html']`,
`//x:a[@href='${ourbigbook.RAW_PREFIX}/subdir/index.html' and text()='index to subdir/index.html']`,
],
[`out/html/subdir.html`]: [
`//x:a[@href='${ourbigbook.DIR_PREFIX}' and text()='link to root']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir' and text()='link to subdir']`,
`//x:a[@href='${ourbigbook.DIR_PREFIX}/subdir/subdir2' and text()='link to subdir2']`,
],
[`out/html/subdir/subdir2.html`]: [
`//x:a[@href='../${ourbigbook.DIR_PREFIX}' and text()='link to root']`,
`//x:a[@href='../${ourbigbook.DIR_PREFIX}/subdir' and text()='link to subdir']`,
`//x:a[@href='../${ourbigbook.DIR_PREFIX}/subdir/subdir2' and text()='link to subdir2']`,
],
[`out/html/${ourbigbook.DIR_PREFIX}/index.html`]: [
`//x:a[@href='../${ourbigbook.FILE_PREFIX}/myfile.txt' and text()='myfile.txt']`,
`//x:a[@href='../${ourbigbook.FILE_PREFIX}/README.bigb' and text()='README.bigb']`,
`//x:a[@href='../${ourbigbook.FILE_PREFIX}/index.html' and text()='index.html']`,
`//x:a[@href='../${ourbigbook.FILE_PREFIX}/_index.html' and text()='_index.html']`,
`//x:a[@href='subdir' and text()='subdir/']`,
],
[`out/html/${ourbigbook.DIR_PREFIX}/subdir/index.html`]: [
`//x:a[@href='../../${ourbigbook.FILE_PREFIX}/subdir/myfile-subdir.txt' and text()='myfile-subdir.txt']`,
`//x:a[@href='../../${ourbigbook.FILE_PREFIX}/subdir/index.html' and text()='index.html']`,
`//x:a[@href='subdir2' and text()='subdir2/']`,
`//x:a[@href='..' and text()='${ourbigbook.FILE_ROOT_PLACEHOLDER}']`,
],
[`out/html/${ourbigbook.DIR_PREFIX}/subdir/subdir2/index.html`]: [
`//x:a[@href='../..' and text()='${ourbigbook.FILE_ROOT_PLACEHOLDER}']`,
`//x:a[@href='..' and text()='subdir']`,
],
},
assert_not_xpath: {
[`out/html/${ourbigbook.DIR_PREFIX}/index.html`]: [
// ../ not added to root listing.
`//x:a[text()='${ourbigbook.FILE_ROOT_PLACEHOLDER}']`,
// Ignored files don't show on listing.
"//x:a[text()='.git']",
"//x:a[text()='.git/']",
],
},
}
)
assert_cli(
'raw: directory listings link to _raw when split pages are turned off',
{
args: ['.'],
filesystem: {
'myfile.txt': `myfile.txt line1
myfile.txt line2
`,
'subdir/myfile-subdir.txt': `myfile-subdir.txt line1
myfile-subdir.txt line2
`,
},
assert_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/myfile.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/myfile-subdir.txt`,
],
assert_not_exists: [
// Don't auto-generate {file} without split
`out/html/${ourbigbook.FILE_PREFIX}/myfile.txt.html`,
`out/html/${ourbigbook.FILE_PREFIX}/subdir/myfile-subdir.txt.html`,
],
assert_xpath: {
[`out/html/${ourbigbook.DIR_PREFIX}/index.html`]: [
// Link to _raw without split. This is a simple behaviour to work reasonably when {file} headers
// don't get their own separate file. The other possibility would be to always autogen without split,
// but then we would have to worry about not adding autogen to db to avoid ID conflicts. Doable as well,
`//x:a[@href='../${ourbigbook.RAW_PREFIX}/myfile.txt.html' and text()='myfile.txt']`,
],
[`out/html/${ourbigbook.DIR_PREFIX}/subdir/index.html`]: [
`//x:a[@href='../../${ourbigbook.RAW_PREFIX}/subdir/myfile-subdir.txt.html' and text()='myfile-subdir.txt']`,
],
},
}
)
assert_cli(
'raw: root directory listing in publish does not show publish',
{
args: ['--dry-run', '--publish'],
pre_exec: MAKE_GIT_REPO_PRE_EXEC,
filesystem: {
'README.bigb': `= Index
`,
'not-ignored.txt': ``,
'ourbigbook.json': `{
}
`,
},
assert_not_xpath: {
[`out/publish/out/github-pages/${ourbigbook.DIR_PREFIX}/index.html`]: [
// ../ not added to root listing.
"//x:a[text()='..']",
],
},
}
)
// ignores
assert_cli(
'json: ignore: is used in conversion',
{
args: ['.'],
filesystem: {
'README.bigb': `= Index
`,
'ignored-top.txt': ``,
'not-ignored.txt': ``,
'a.ignore': ``,
'subdir/ignored.txt': ``,
'subdir/ignored-top.txt': ``,
'subdir/not-ignored.txt': ``,
'subdir/a.ignore': ``,
'subdir-dont/a.ignore': ``,
'subdir-dont/subdir/a.ignore': ``,
'subdir-ignored/default.txt': ``,
// All files of this subdir are ignored, but not the subdir itself.
'subdir-ignore-files/a.ignore': ``,
'ourbigbook.json': `{
"ignore": [
"ignored-top\\\\.txt",
"subdir/ignored\\\\.txt",
"subdir-ignored",
".*\\\\.ignore"
],
"dontIgnore": [
"subdir-dont/.*\\\\.ignore"
]
}
`,
},
assert_exists: [
`out/html/${ourbigbook.DIR_PREFIX}/index.html`,
`out/html/${ourbigbook.RAW_PREFIX}/not-ignored.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/not-ignored.txt`,
// Only applies to full matches.
`out/html/${ourbigbook.RAW_PREFIX}/subdir/ignored-top.txt`,
// dontIgnore overrides previous ignores.
`out/html/${ourbigbook.RAW_PREFIX}/subdir-dont/a.ignore`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir-dont/subdir/a.ignore`,
// Directory conversion does not blow up when all files in directory are ignored.
`out/html/${ourbigbook.DIR_PREFIX}/subdir-ignore-files/index.html`,
],
assert_not_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/ignored-top.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/ignored.txt`,
// If a directory is ignored, we don't recurse into it at all.
`out/html/${ourbigbook.DIR_PREFIX}/subdir-ignored/index.html`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir-ignored/default.txt`,
// Ignore by extension.
`out/html/${ourbigbook.RAW_PREFIX}/a.ignore`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/a.ignore`,
],
}
)
assert_cli(
'json: ignore is used in publish',
{
args: ['--dry-run', '--publish'],
pre_exec: MAKE_GIT_REPO_PRE_EXEC,
filesystem: {
'README.bigb': `= Index
`,
'ignored.txt': ``,
'not-ignored.txt': ``,
'ourbigbook.json': `{
"ignore": [
"ignored.txt"
]
}
`,
},
assert_exists: [
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/not-ignored.txt`,
],
assert_not_exists: [
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/ignored.txt`,
],
}
)
assert_cli(
'json: ignore: works if pointing to ignored directory',
{
args: ['ignored'],
filesystem: {
'ignored/notindex.bigb': `\\reserved_undefined
`,
'ourbigbook.json': `{
"ignore": [
"ignored"
]
}
`,
},
}
)
assert_cli(
'json: ignore: works if pointing inside ignored directory',
{
args: ['ignored/subdir'],
filesystem: {
'ignored/subdir/notindex.bigb': `\\reserved_undefined
`,
'ourbigbook.json': `{
"ignore": [
"ignored"
]
}
`,
},
}
)
// ignores
assert_cli(
'json: ignoreConvert: ignores files for convertion but adds them on listings',
{
args: ['.'],
filesystem: {
'README.bigb': `= Index
`,
'bigb-ignored.bigb': `= Bigb ignored
`,
'scss-ignored.scss': ``,
'scss-not-ignored.scss': ``,
'subdir-ignored/style.scss': ``,
'ourbigbook.json': `{
"ignoreConvert": [
"bigb-ignored\\\\.bigb",
"scss-ignored\\\\.scss",
"subdir-ignored"
]
}
`,
},
assert_exists: [
`out/html/${ourbigbook.DIR_PREFIX}/index.html`,
`out/html/${ourbigbook.RAW_PREFIX}/bigb-ignored.bigb`,
`out/html/${ourbigbook.RAW_PREFIX}/scss-ignored.scss`,
`out/html/${ourbigbook.RAW_PREFIX}/scss-not-ignored.scss`,
`out/html/${ourbigbook.RAW_PREFIX}/scss-not-ignored.css`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir-ignored/style.scss`,
],
assert_not_exists: [
`out/html/bigb-ignored.html`,
`out/html/${ourbigbook.RAW_PREFIX}/scss-ignored.css`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir-ignored/style.css.`,
],
}
)
assert_cli(
'json: dontIgnoreConvert: overrides ignoreConvert basic',
{
args: ['.'],
filesystem: {
'scss-dont.scss': ``,
'scss-ignored.scss': ``,
'subdir/scss-ignored.scss': ``,
'ourbigbook.json': `{
"ignoreConvert": [
".*\\\\.scss"
],
"dontIgnoreConvert": [
"scss-dont.scss"
]
}
`,
},
assert_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/scss-dont.scss`,
`out/html/${ourbigbook.RAW_PREFIX}/scss-dont.css`,
`out/html/${ourbigbook.RAW_PREFIX}/scss-ignored.scss`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/scss-ignored.scss`,
],
assert_not_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/scss-ignored.css`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/scss-ignored.css.`,
],
}
)
assert_cli(
'json: dontIgnoreConvert: overrides ignoreConvert for publish',
{
args: ['--dry-run', '--publish'],
pre_exec: publish_pre_exec,
filesystem: {
'scss-dont.scss': ``,
'scss-ignored.scss': ``,
'subdir/scss-ignored.scss': ``,
'ourbigbook.json': `{
"ignoreConvert": [
".*\\\\.scss"
],
"dontIgnoreConvert": [
"scss-dont.scss"
]
}
`,
},
assert_exists: [
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/scss-dont.scss`,
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/scss-dont.css`,
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/scss-ignored.scss`,
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/subdir/scss-ignored.scss`,
],
assert_not_exists: [
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/scss-ignored.css`,
`out/publish/out/github-pages/${ourbigbook.RAW_PREFIX}/subdir/scss-ignored.css.`,
],
}
)
assert_cli(
// https://github.com/ourbigbook/ourbigbook/issues/253
'git: .gitignore ignores files from toplevel directory conversion',
{
args: ['.'],
pre_exec: MAKE_GIT_REPO_PRE_EXEC,
filesystem: {
'README.bigb': `= Index
`,
'ignored.txt': ``,
'not-ignored.txt': ``,
'subdir/ignored.txt': ``,
'subdir/not-ignored.txt': ``,
'ignored-subdir/1.txt': ``,
'ignored-subdir/2.txt': ``,
'.gitignore': `ignored.txt
ignored-subdir
`,
'ourbigbook.json': `{}`,
},
assert_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/not-ignored.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/not-ignored.txt`,
],
assert_not_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/ignored.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/ignored.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/ignored-subdir/1.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/ignored-subdir/2.txt`,
],
}
)
assert_cli(
// https://github.com/ourbigbook/ourbigbook/issues/253
'git: .gitignore ignores files from subdirectory conversion',
{
args: ['subdir'],
pre_exec: MAKE_GIT_REPO_PRE_EXEC,
filesystem: {
'README.bigb': `= Index
`,
'ignored.txt': ``,
'not-ignored.txt': ``,
'subdir/ignored.txt': ``,
'subdir/not-ignored.txt': ``,
'.gitignore': `ignored.txt
ignored-subdir
`,
'ourbigbook.json': `{
}
`,
},
assert_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/subdir/not-ignored.txt`,
],
assert_not_exists: [
`out/html/${ourbigbook.RAW_PREFIX}/not-ignored.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/ignored.txt`,
`out/html/${ourbigbook.RAW_PREFIX}/subdir/ignored.txt`,
],
}
)
assert_cli(
// https://github.com/ourbigbook/ourbigbook/issues/253
'git: .gitignore ignores individual files from conversion',
{
args: ['tmp.bigb'],
pre_exec: MAKE_GIT_REPO_PRE_EXEC,
filesystem: {
'README.bigb': `= Index
`,
'tmp.bigb': `= Tmp
\\asdf
`,
'.gitignore': `tmp.bigb
`,
'ourbigbook.json': `{
}
`,
},
}
)
assert_cli(
'git: .gitignore is used in --web conversion',
{
args: ['--web', '--web-dry', '.'],
pre_exec: MAKE_GIT_REPO_PRE_EXEC,
filesystem: {
'README.bigb': `= Index
`,
'tmp.bigb': `= Tmp
\\asdf
`,
'.gitignore': `tmp.bigb
`,
'ourbigbook.json': `{
}
`,
},
}
)
assert_cli(
'git: conversion of single file in git directory works',
{
args: ['README.bigb'],
pre_exec: MAKE_GIT_REPO_PRE_EXEC,
filesystem: {
'README.bigb': `= Index
`,
'.gitignore': `tmp.bigb
`,
'ourbigbook.json': `{
}
`,
},
assert_exists: [
`out/html/index.html`,
],
}
)
assert_cli(
'--web-dry on simple repository',
{
args: ['--web', '--web-dry', '.'],
filesystem: {
'README.bigb': `= Index
`,
'ourbigbook.json': `{
}
`,
},
}
)
assert_cli(
'--web-dry on single file',
{
args: ['--web', '--web-dry', 'README.bigb'],
filesystem: {
'README.bigb': `= Index
`,
'ourbigbook.json': `{
}
`,
},
}
)
// This doesn't really test anything as options are not doing anything to ourbigbook, only lib.
//assert_cli(
// "ourbigbook.json: publishOptions are not active when not publishing",
// {
// args: ['.'],
// filesystem: {
// 'README.bigb': `= Index
//`,
// 'ourbigbook.json': `{
// "publishOptions": {
// "ignore": [
// "README.bigb"
// ]
// },
//}
//`,
// },
// assert_exists: [
// `out/html/index.html`,
// ],
// }
//);
assert_cli('file: _file auto-generation conversion image media provider works',
{
args: ['-S', 'project'],
filesystem: {
'project/myimg.png': `aaa`,
'media/outside.png': `aaa`,
'project/ourbigbook.json': `{
"media-providers": {
"github": {
"default-for": ["image"],
"title-from-src": false,
"path": "../media",
"remote": "ourbigbook/ourbigbook-media"
}
}
}`
},
assert_xpath: {
[`project/out/html/${ourbigbook.FILE_PREFIX}/myimg.png.html`]: [
`//x:img[@src='../${ourbigbook.RAW_PREFIX}/myimg.png']`,
],
},
},
)