diff --git a/src/compile/build.ts b/src/compile/build.ts index 37a8b513d..1f4448d1c 100644 --- a/src/compile/build.ts +++ b/src/compile/build.ts @@ -1,6 +1,5 @@ import * as vscode from 'vscode' import * as path from 'path' -import * as cs from 'cross-spawn' import { pickRootPath } from '../utils/quick-pick' import { lw } from '../lw' import type { ProcessEnv, RecipeStep, Step } from '../types' @@ -143,6 +142,7 @@ async function build(skipSelection: boolean = false, rootFile: string | undefine */ async function buildLoop() { if (isBuilding) { + logger.log('Another build loop is already running.') return } @@ -156,16 +156,15 @@ async function buildLoop() { if (step === undefined) { break } - lw.compile.lastSteps.push(step) const env = spawnProcess(step) const success = await monitorProcess(step, env) - skipped = skipped && !(step.isExternal || !step.isSkipped) + skipped = skipped && !step.isExternal && step.isSkipped if (success && queue.isLastStep(step)) { await afterSuccessfulBuilt(step, skipped) } } isBuilding = false - setTimeout(() => lw.compile.compiledPDFWriting--, 1000) + setTimeout(() => lw.compile.compiledPDFWriting--, vscode.workspace.getConfiguration('latex-workshop').get('latex.watch.pdf.delay') as number) } /** @@ -208,9 +207,9 @@ function spawnProcess(step: Step): ProcessEnv { const args = step.args if (args && !step.name.endsWith(lw.constant.MAGIC_PROGRAM_ARGS_SUFFIX)) { // All optional arguments are given as a unique string (% !TeX options) if any, so we use {shell: true} - lw.compile.process = cs.spawn(`${step.command} ${args[0]}`, [], {cwd: path.dirname(step.rootFile), env, shell: true}) + lw.compile.process = lw.external.spawn(`${step.command} ${args[0]}`, [], {cwd: path.dirname(step.rootFile), env, shell: true}) } else { - lw.compile.process = cs.spawn(step.command, args, {cwd: path.dirname(step.rootFile), env}) + lw.compile.process = lw.external.spawn(step.command, args ?? [], {cwd: path.dirname(step.rootFile), env}) } } else if (!step.isExternal) { let cwd = path.dirname(step.rootFile) @@ -218,10 +217,10 @@ function spawnProcess(step: Step): ProcessEnv { cwd = lw.root.dir.path } logger.log(`cwd: ${cwd}`) - lw.compile.process = cs.spawn(step.command, step.args, {cwd, env}) + lw.compile.process = lw.external.spawn(step.command, step.args ?? [], {cwd, env}) } else { logger.log(`cwd: ${step.cwd}`) - lw.compile.process = cs.spawn(step.command, step.args, {cwd: step.cwd}) + lw.compile.process = lw.external.spawn(step.command, step.args ?? [], {cwd: step.cwd}) } logger.log(`LaTeX build process spawned with PID ${lw.compile.process.pid}.`) return env @@ -245,13 +244,13 @@ async function monitorProcess(step: Step, env: ProcessEnv): Promise { return false } let stdout = '' - lw.compile.process.stdout.on('data', (msg: Buffer | string) => { + lw.compile.process.stdout?.on('data', (msg: Buffer | string) => { stdout += msg logger.logCompiler(msg.toString()) }) let stderr = '' - lw.compile.process.stderr.on('data', (msg: Buffer | string) => { + lw.compile.process.stderr?.on('data', (msg: Buffer | string) => { stderr += msg logger.logCompiler(msg.toString()) }) @@ -384,7 +383,7 @@ function handleNoRetryError(configuration: vscode.WorkspaceConfiguration, step: * shows an error message to the user and clears the BuildToolQueue. */ function handleExternalCommandError() { - logger.log(`Build returns with error on PID ${lw.compile.process?.pid}.`) + logger.log(`Build with external command returns error on PID ${lw.compile.process?.pid}.`) logger.refreshStatus('x', 'errorForeground', undefined, 'warning') void logger.showErrorMessageWithCompilerLogButton('Build terminated with error.') queue.clear() diff --git a/src/compile/index.ts b/src/compile/index.ts index 8bbd91fbb..b063dca8c 100644 --- a/src/compile/index.ts +++ b/src/compile/index.ts @@ -1,5 +1,4 @@ -import type { ChildProcessWithoutNullStreams } from 'child_process' -import type { Step } from '../types' +import type { ChildProcess } from 'child_process' import { build, autoBuild } from './build' import { terminate } from './terminate' @@ -7,9 +6,8 @@ export const compile = { build, autoBuild, terminate, - lastSteps: [] as Step[], lastAutoBuildTime: 0, compiledPDFPath: '', compiledPDFWriting: 0, - process: undefined as ChildProcessWithoutNullStreams | undefined + process: undefined as ChildProcess | undefined } diff --git a/src/compile/recipe.ts b/src/compile/recipe.ts index ccdd2df7e..b71b9a4d5 100644 --- a/src/compile/recipe.ts +++ b/src/compile/recipe.ts @@ -303,8 +303,8 @@ function findRecipe(rootFile: string, langId: string, recipeName?: string): Reci } } // Find default recipe of last used - if (recipe === undefined && defaultRecipeName === 'lastUsed' && recipes.find(candidate => candidate.name === state.prevRecipe?.name)) { - recipe = state.prevRecipe + if (recipe === undefined && defaultRecipeName === 'lastUsed') { + recipe = recipes.find(candidate => candidate.name === state.prevRecipe?.name) } // If still not found, fallback to 'first' if (recipe === undefined) { diff --git a/src/compile/terminate.ts b/src/compile/terminate.ts index 0d7af7f65..7527ba1ed 100644 --- a/src/compile/terminate.ts +++ b/src/compile/terminate.ts @@ -2,7 +2,7 @@ import * as cp from 'child_process' import { lw } from '../lw' import { queue } from './queue' -const logger = lw.log('Build', 'Recipe') +const logger = lw.log('Build', 'Terminate') /** * Terminate the current process of LaTeX building. This OS-specific function diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 213665c8b..7d3f79d07 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -127,6 +127,7 @@ function initStatusBarItem() { function clearCompilerMessage() { COMPILER_PANEL.clear() + CACHED_COMPILER.length = 0 } function showLog() { diff --git a/test/suites/utils.ts b/test/suites/utils.ts index 165feafe3..6f51dbe51 100644 --- a/test/suites/utils.ts +++ b/test/suites/utils.ts @@ -79,7 +79,6 @@ export function sleep(ms: number) { export async function reset() { await vscode.commands.executeCommand('workbench.action.closeAllEditors') await Promise.all(Object.values(lw.cache.promises)) - lw.compile.lastSteps = [] lw.compile.lastAutoBuildTime = 0 lw.compile.compiledPDFPath = '' lw.compile.compiledPDFWriting = 0 diff --git a/test/units/01_core_file.test.ts b/test/units/01_core_file.test.ts index d80a29b38..a6ab7ddba 100644 --- a/test/units/01_core_file.test.ts +++ b/test/units/01_core_file.test.ts @@ -10,7 +10,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const fixture = path.basename(__filename).split('.')[0] before(() => { - mock.object(lw, 'file') + mock.init(lw, 'file') }) after(() => { @@ -79,82 +79,82 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.pathStrictEqual(lw.file.getOutDir(texPath), rootDir) }) - it('should get output directory with absolute `latex.outDir` and root', async () => { - await set.config('latex.outDir', '/output') + it('should get output directory with absolute `latex.outDir` and root', () => { + set.config('latex.outDir', '/output') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), '/output') }) - it('should get output directory with relative `latex.outDir` and root', async () => { - await set.config('latex.outDir', 'output') + it('should get output directory with relative `latex.outDir` and root', () => { + set.config('latex.outDir', 'output') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), 'output') }) - it('should get output directory with relative `latex.outDir` with leading `./` and root', async () => { - await set.config('latex.outDir', './output') + it('should get output directory with relative `latex.outDir` with leading `./` and root', () => { + set.config('latex.outDir', './output') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), 'output') }) - it('should get output directory with relative `latex.outDir`, root, and an input latex', async () => { + it('should get output directory with relative `latex.outDir`, root, and an input latex', () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.outDir', 'output') + set.config('latex.outDir', 'output') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(texPath), 'output') }) - it('should get output directory with placeholder in `latex.outDir` and root', async () => { - await set.config('latex.outDir', '%DIR%') + it('should get output directory with placeholder in `latex.outDir` and root', () => { + set.config('latex.outDir', '%DIR%') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), lw.root.dir.path) }) - it('should get output directory with placeholder in `latex.outDir`, root, and an input latex', async () => { + it('should get output directory with placeholder in `latex.outDir`, root, and an input latex', () => { const rootDir = get.path(fixture) const texPath = get.path(fixture, 'main.tex') - await set.config('latex.outDir', '%DIR%') + set.config('latex.outDir', '%DIR%') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(texPath), rootDir) }) - it('should get output directory from last compilation if `latex.outDir` is `%DIR%`', async () => { - await set.config('latex.outDir', '%DIR%') + it('should get output directory from last compilation if `latex.outDir` is `%DIR%`', () => { + set.config('latex.outDir', '%DIR%') set.root(fixture, 'main.tex') lw.file.setTeXDirs(lw.root.file.path ?? '', '/output') assert.pathStrictEqual(lw.file.getOutDir(), '/output') }) - it('should ignore output directory from last compilation if `latex.outDir` is not `%DIR%`', async () => { - await set.config('latex.outDir', '/output') + it('should ignore output directory from last compilation if `latex.outDir` is not `%DIR%`', () => { + set.config('latex.outDir', '/output') set.root(fixture, 'main.tex') lw.file.setTeXDirs(lw.root.file.path ?? '', '/trap') assert.pathStrictEqual(lw.file.getOutDir(), '/output') }) - it('should ignore output directory from last compilation if no `outdir` is recorded', async () => { - await set.config('latex.outDir', '%DIR%') + it('should ignore output directory from last compilation if no `outdir` is recorded', () => { + set.config('latex.outDir', '%DIR%') set.root(fixture, 'main.tex') lw.file.setTeXDirs(lw.root.file.path ?? '') assert.pathStrictEqual(lw.file.getOutDir(), lw.root.dir.path) }) - it('should handle empty `latex.outDir` correctly', async () => { - await set.config('latex.outDir', '') + it('should handle empty `latex.outDir` correctly', () => { + set.config('latex.outDir', '') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), './') }) - it('should handle absolute `latex.outDir` with trailing slashes correctly', async () => { - await set.config('latex.outDir', '/output/') + it('should handle absolute `latex.outDir` with trailing slashes correctly', () => { + set.config('latex.outDir', '/output/') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), '/output') }) - it('should handle relative `latex.outDir` with trailing slashes correctly', async () => { - await set.config('latex.outDir', 'output/') + it('should handle relative `latex.outDir` with trailing slashes correctly', () => { + set.config('latex.outDir', 'output/') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), 'output') }) @@ -184,7 +184,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const texPath = get.path(fixture, 'main.tex') const flsPath = get.path(fixture, 'output', 'main.fls') - await set.config('latex.outDir', 'output') + set.config('latex.outDir', 'output') assert.pathStrictEqual(await lw.file.getFlsPath(texPath), flsPath) }) @@ -233,18 +233,18 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { ]) }) - it('should correctly find BibTeX files in `latex.bibDirs`', async () => { + it('should correctly find BibTeX files in `latex.bibDirs`', () => { set.root(fixture, 'main.tex') - await set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ]) + set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ]) const result = lw.file.getBibPath('sub.bib', lw.root.dir.path ?? '') assert.listStrictEqual(result, [ path.resolve(lw.root.dir.path ?? '', 'subdir', 'sub.bib') ]) }) - it('should return an empty array when no BibTeX file is found', async () => { + it('should return an empty array when no BibTeX file is found', () => { set.root(fixture, 'main.tex') - await set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ]) + set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ]) const result = lw.file.getBibPath('nonexistent.bib', path.resolve(lw.root.dir.path ?? '', 'output')) assert.listStrictEqual(result, [ ]) }) @@ -258,29 +258,29 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { ]) }) - it('should handle case when kpsewhich is disabled and BibTeX file not found', async () => { + it('should handle case when kpsewhich is disabled and BibTeX file not found', () => { const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: get.path(fixture, 'nonexistent.bib'), output: [''], stderr: '', signal: 'SIGTERM' }) - await set.config('kpsewhich.bibtex.enabled', false) + set.config('kpsewhich.bibtex.enabled', false) set.root(fixture, 'main.tex') const result = lw.file.getBibPath('nonexistent.bib', lw.root.dir.path ?? '') stub.restore() assert.listStrictEqual(result, [ ]) }) - it('should handle case when kpsewhich is enabled and BibTeX file not found', async () => { + it('should handle case when kpsewhich is enabled and BibTeX file not found', () => { const nonPath = get.path(fixture, 'nonexistent.bib') const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: get.path(fixture, 'nonexistent.bib'), output: [''], stderr: '', signal: 'SIGTERM' }) - await set.config('kpsewhich.bibtex.enabled', true) + set.config('kpsewhich.bibtex.enabled', true) set.root(fixture, 'main.tex') const result = lw.file.getBibPath('nonexistent.bib', lw.root.dir.path ?? '') stub.restore() assert.listStrictEqual(result, [ nonPath ]) }) - it('should return an empty array when kpsewhich is enabled but file is not found', async () => { + it('should return an empty array when kpsewhich is enabled but file is not found', () => { const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: '', output: [''], stderr: '', signal: 'SIGTERM' }) - await set.config('kpsewhich.bibtex.enabled', true) + set.config('kpsewhich.bibtex.enabled', true) set.root(fixture, 'main.tex') const result = lw.file.getBibPath('another-nonexistent.bib', lw.root.dir.path ?? '') stub.restore() @@ -330,40 +330,40 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.file.getJobname', () => { - it('should return the jobname if present in configuration', async () => { + it('should return the jobname if present in configuration', () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.jobname', 'myJob') + set.config('latex.jobname', 'myJob') assert.strictEqual(lw.file.getJobname(texPath), 'myJob') }) - it('should return the name of the input texPath if jobname is empty', async () => { + it('should return the name of the input texPath if jobname is empty', () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.jobname', '') + set.config('latex.jobname', '') const expectedJobname = path.parse(texPath).name assert.strictEqual(lw.file.getJobname(texPath), expectedJobname) }) - it('should return the name of the input texPath if configuration is not set', async () => { + it('should return the name of the input texPath if configuration is not set', () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.jobname', undefined) // Ensuring the jobname is not set + set.config('latex.jobname', undefined) // Ensuring the jobname is not set const expectedJobname = path.parse(texPath).name assert.strictEqual(lw.file.getJobname(texPath), expectedJobname) }) }) describe('lw.file.getPdfPath', () => { - it('should return the correct PDF path when outDir is empty', async () => { - await set.config('latex.outDir', '') + it('should return the correct PDF path when outDir is empty', () => { + set.config('latex.outDir', '') set.root(fixture, 'main.tex') const texpath = lw.root.file.path ?? '' assert.pathStrictEqual(lw.file.getPdfPath(texpath), texpath.replaceAll('.tex', '.pdf')) }) - it('should return the correct PDF path when outDir is specified', async () => { - await set.config('latex.outDir', 'output') + it('should return the correct PDF path when outDir is specified', () => { + set.config('latex.outDir', 'output') set.root(fixture, 'main.tex') const texpath = lw.root.file.path ?? '' assert.pathStrictEqual(lw.file.getPdfPath(texpath), texpath.replaceAll('main.tex', 'output/main.pdf')) @@ -503,16 +503,16 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('kpsewhich', () => { - it('should call kpsewhich with correct arguments', async () => { - await set.config('kpsewhich.path', 'kpse') + it('should call kpsewhich with correct arguments', () => { + set.config('kpsewhich.path', 'kpse') const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: '', output: [''], stderr: '', signal: 'SIGTERM' }) lw.file.kpsewhich('article.cls') stub.restore() sinon.assert.calledWith(stub, 'kpse', ['article.cls'], sinon.match.any) }) - it('should handle isBib flag correctly', async () => { - await set.config('kpsewhich.path', 'kpse') + it('should handle isBib flag correctly', () => { + set.config('kpsewhich.path', 'kpse') const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: '', output: [''], stderr: '', signal: 'SIGTERM' }) lw.file.kpsewhich('reference.bib', true) stub.restore() diff --git a/test/units/02_core_watcher.test.ts b/test/units/02_core_watcher.test.ts index c3c9a0c9e..2d4059aa7 100644 --- a/test/units/02_core_watcher.test.ts +++ b/test/units/02_core_watcher.test.ts @@ -18,7 +18,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const getOnDeleteHandlers = () => _onDeleteHandlersSpy.call(lw.watcher.src) as Set<(uri: vscode.Uri) => void> before(() => { - mock.object(lw, 'file', 'watcher') + mock.init(lw, 'file', 'watcher') _onDidChangeSpy = sinon.spy(lw.watcher.src as any, 'onDidChange') _onDidDeleteSpy = sinon.spy(lw.watcher.src as any, 'onDidDelete') _watchersSpy = sinon.spy(lw.watcher.src as any, 'watchers', ['get']).get @@ -210,10 +210,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const stub = sinon.stub() const handler = (filePath: vscode.Uri) => { stub(filePath.fsPath) } - beforeEach(async () => { + beforeEach(() => { stub.reset() lw.watcher.src.onDelete(handler) - await set.config('latex.watch.delay', 100) + set.config('latex.watch.delay', 100) }) afterEach(() => { diff --git a/test/units/03_core_cache.test.ts b/test/units/03_core_cache.test.ts index cf908a2d0..c82e92862 100644 --- a/test/units/03_core_cache.test.ts +++ b/test/units/03_core_cache.test.ts @@ -9,7 +9,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const fixture = path.basename(__filename).split('.')[0] before(() => { - mock.object(lw, 'file', 'watcher', 'cache') + mock.init(lw, 'file', 'watcher', 'cache') }) after(() => { @@ -38,7 +38,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should excluded files with config set ', async () => { - await set.config('latex.watch.files.ignore', ['**/*.bbl']) + set.config('latex.watch.files.ignore', ['**/*.bbl']) log.start() await lw.cache.refreshCache(bblPath) @@ -53,8 +53,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.cache.canCache', () => { - beforeEach(async () => { - await set.config('latex.watch.files.ignore', []) + beforeEach(() => { + set.config('latex.watch.files.ignore', []) }) it('should cache supported TeX files', async () => { @@ -284,9 +284,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.cache.refreshCacheAggressive', () => { - beforeEach(async () => { - await set.config('intellisense.update.aggressive.enabled', true) - await set.config('intellisense.update.delay', 100) + beforeEach(() => { + set.config('intellisense.update.aggressive.enabled', true) + set.config('intellisense.update.delay', 100) }) it('should not aggressively cache non-cached files', async function (this: Mocha.Context) { @@ -335,7 +335,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { this.slow(350) const texPath = get.path(fixture, 'main.tex') - await set.config('intellisense.update.aggressive.enabled', false) + set.config('intellisense.update.aggressive.enabled', false) lw.cache.add(texPath) await lw.cache.refreshCache(texPath) const stub = mock.textDocument(texPath, '', { isDirty: true }) @@ -550,7 +550,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { it('should add a child if it is defined in `latex.texDirs`', async () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.texDirs', [ get.path(fixture, 'update_children_xr', 'sub') ]) + set.config('latex.texDirs', [ get.path(fixture, 'update_children_xr', 'sub') ]) set.root(texPath) lw.cache.add(texPath) @@ -775,7 +775,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not add \\bibdata if the bib is excluded', async () => { - await set.config('latex.watch.files.ignore', ['**/main.bib']) + set.config('latex.watch.files.ignore', ['**/main.bib']) const toParse = get.path(fixture, 'load_aux_file', 'main.tex') set.root(fixture, 'load_aux_file', 'main.tex') await lw.cache.refreshCache(toParse) diff --git a/test/units/04_core_root.test.ts b/test/units/04_core_root.test.ts index 3abf24640..445ccbded 100644 --- a/test/units/04_core_root.test.ts +++ b/test/units/04_core_root.test.ts @@ -8,7 +8,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const fixture = path.basename(__filename).split('.')[0] before(() => { - mock.object(lw, 'file', 'watcher', 'cache', 'root') + mock.init(lw, 'file', 'watcher', 'cache', 'root') }) after(() => { @@ -16,8 +16,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('on root file deletion', () => { - beforeEach(async () => { - await set.config('latex.watch.delay', 100) + beforeEach(() => { + set.config('latex.watch.delay', 100) }) afterEach(() => { @@ -271,8 +271,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.root.findFromActive', () => { - beforeEach(async () => { - await set.config('latex.rootFile.indicator', '\\documentclass[]{}') + beforeEach(() => { + set.config('latex.rootFile.indicator', '\\documentclass[]{}') }) it('should do nothing if there is no active editor', async () => { @@ -341,7 +341,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { describe('lw.root.getIndicator', () => { it('should use \\begin{document} indicator on selecting `\\begin{document}`', async () => { - await set.config('latex.rootFile.indicator', '\\begin{document}') + set.config('latex.rootFile.indicator', '\\begin{document}') const texPath = get.path(fixture, 'main.tex') @@ -354,7 +354,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should return \\documentclass indicator on other values', async () => { - await set.config('latex.rootFile.indicator', 'invalid value') + set.config('latex.rootFile.indicator', 'invalid value') const texPath = get.path(fixture, 'main.tex') @@ -368,19 +368,19 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.root.findInWorkspace', () => { - beforeEach(async () => { - await set.config('latex.rootFile.indicator', '\\begin{document}') // avoid active editor check + beforeEach(() => { + set.config('latex.rootFile.indicator', '\\begin{document}') // avoid active editor check }) it('should follow `latex.search.rootFiles.include` config', async () => { - await set.config('latex.search.rootFiles.include', [ 'absolutely-nothing.tex' ]) + set.config('latex.search.rootFiles.include', [ 'absolutely-nothing.tex' ]) await lw.root.find() assert.strictEqual(lw.root.file.path, undefined) }) it('should follow `latex.search.rootFiles.exclude` config', async () => { - await set.config('latex.search.rootFiles.exclude', [ '**/*' ]) + set.config('latex.search.rootFiles.exclude', [ '**/*' ]) await lw.root.find() assert.strictEqual(lw.root.file.path, undefined) @@ -389,8 +389,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { it('should find the correct root from workspace', async () => { const texPath = get.path(fixture, 'find_workspace', 'main.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) - await set.config('latex.search.rootFiles.exclude', [ `${fixture}/find_workspace/**/parent.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.exclude', [ `${fixture}/find_workspace/**/parent.tex` ]) await lw.root.find() assert.hasLog('Try finding root from current workspaceRootDir:') @@ -398,7 +398,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should ignore root file indicators in comments', async () => { - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/comment.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/comment.tex` ]) await lw.root.find() assert.strictEqual(lw.root.file.path, undefined) @@ -408,7 +408,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const texPath = get.path(fixture, 'find_workspace', 'main.tex') const texPathAnother = get.path(fixture, 'find_workspace', 'another.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) const stub = mock.activeTextEditor(texPathAnother, '\\documentclass{article}\n') await lw.root.find() stub.restore() @@ -421,8 +421,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const texPath = get.path(fixture, 'find_workspace', 'parent.tex') const texPathAnother = get.path(fixture, 'find_workspace', 'another.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) - await set.config('latex.search.rootFiles.exclude', [ `${fixture}/find_workspace/main.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.exclude', [ `${fixture}/find_workspace/main.tex` ]) await lw.cache.refreshCache(texPath) const stub = mock.activeTextEditor(texPathAnother, '\\documentclass{article}\n') await lw.root.find() @@ -436,7 +436,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const texPath = get.path(fixture, 'find_workspace', 'parent.tex') const texPathAnother = get.path(fixture, 'find_workspace', 'another.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) await lw.cache.refreshCache(texPath) const stub = mock.activeTextEditor(texPathAnother, '\\documentclass{article}\n') await lw.root.find() @@ -449,7 +449,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { it('should find the correct root if current root is in the candidates', async () => { const texPath = get.path(fixture, 'find_workspace', 'main.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) set.root(fixture, 'find_workspace', 'main.tex') const stub = mock.activeTextEditor(texPath, '\\documentclass{article}\n') await lw.root.find() @@ -468,7 +468,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not change root if no new root can be found, only refresh outline', async () => { - await set.config('latex.search.rootFiles.exclude', [ '**/*.*' ]) + set.config('latex.search.rootFiles.exclude', [ '**/*.*' ]) await lw.root.find() diff --git a/test/units/05_compile_queue.test.ts b/test/units/05_compile_queue.test.ts index 7548f962f..37dda6af3 100644 --- a/test/units/05_compile_queue.test.ts +++ b/test/units/05_compile_queue.test.ts @@ -6,7 +6,7 @@ import { queue } from '../../src/compile/queue' describe(path.basename(__filename).split('.')[0] + ':', () => { before(() => { - mock.object(lw, 'file', 'root') + mock.init(lw, 'file', 'root') }) after(() => { diff --git a/test/units/06_compile_recipe.test.ts b/test/units/06_compile_recipe.test.ts index b860c454b..ec6af22a3 100644 --- a/test/units/06_compile_recipe.test.ts +++ b/test/units/06_compile_recipe.test.ts @@ -7,22 +7,21 @@ import { build, initialize } from '../../src/compile/recipe' import { queue } from '../../src/compile/queue' describe(path.basename(__filename).split('.')[0] + ':', () => { - const fixture = path.basename(__filename).split('.')[0] let getOutDirStub: sinon.SinonStub let getIncludedTeXStub: sinon.SinonStub let mkdirStub: sinon.SinonStub before(() => { - mock.object(lw, 'file', 'root') + mock.init(lw, 'file', 'root') getOutDirStub = sinon.stub(lw.file, 'getOutDir').returns('.') getIncludedTeXStub = lw.cache.getIncludedTeX as sinon.SinonStub mkdirStub = sinon.stub(lw.external, 'mkdirSync').returns(undefined) }) - beforeEach(async () => { + beforeEach(() => { initialize() getIncludedTeXStub.returns([]) - await set.config('latex.recipe.default', 'first') + set.config('latex.recipe.default', 'first') }) afterEach(() => { @@ -42,7 +41,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { this.slow(500) const expectedImageName = 'your-docker-image' - await set.config('docker.image.latex', expectedImageName) + await set.codeConfig('docker.image.latex', expectedImageName) await sleep(150) assert.strictEqual(process.env['LATEXWORKSHOP_DOCKER_LATEX'], expectedImageName) @@ -52,7 +51,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { this.slow(500) const expectedDockerPath = '/usr/local/bin/docker' - await set.config('docker.path', expectedDockerPath) + await set.codeConfig('docker.path', expectedDockerPath) await sleep(150) assert.strictEqual(process.env['LATEXWORKSHOP_DOCKER_PATH'], expectedDockerPath) @@ -62,7 +61,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { describe('lw.compile->recipe.build', () => { it('should call `saveAll` before building', async () => { const stub = sinon.stub(vscode.workspace, 'saveAll') as sinon.SinonStub - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) stub.restore() @@ -71,10 +70,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should call `createOutputSubFolders` with correct args', async () => { - const rootFile = set.root(fixture, 'main.tex') - const subPath = get.path(fixture, 'sub', 'main.tex') - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) + const rootFile = set.root('main.tex') + const subPath = get.path('sub', 'main.tex') + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) lw.root.subfiles.path = subPath getIncludedTeXStub.returns([rootFile, subPath]) @@ -83,10 +82,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should call `createOutputSubFolders` with correct args with subfiles package', async () => { - const rootFile = set.root(fixture, 'main.tex') - const subPath = get.path(fixture, 'sub', 'main.tex') - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) + const rootFile = set.root('main.tex') + const subPath = get.path('sub', 'main.tex') + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) lw.root.subfiles.path = subPath getIncludedTeXStub.returns([rootFile, subPath]) @@ -95,9 +94,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not call buildLoop if no tool is created', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.tools', []) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool'] }]) + const rootFile = set.root('main.tex') + set.config('latex.tools', []) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool'] }]) const stub = sinon.stub() await build(rootFile, 'latex', stub) @@ -106,9 +105,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should set lw.compile.compiledPDFPath', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) + const rootFile = set.root('main.tex') + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) await build(rootFile, 'latex', async () => {}) @@ -128,8 +127,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should do nothing but log an error if no recipe is found', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.recipes', []) + const rootFile = set.root('main.tex') + set.config('latex.recipes', []) await build(rootFile, 'latex', async () => {}) @@ -137,11 +136,11 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should create build tools based on magic comments when enabled', async () => { - const rootFile = set.root(fixture, 'magic.tex') + const rootFile = set.root('magic.tex') readStub.resolves('% !TEX program = pdflatex\n') - await set.config('latex.recipes', []) - await set.config('latex.build.forceRecipeUsage', false) - await set.config('latex.magic.args', ['--shell-escape']) + set.config('latex.recipes', []) + set.config('latex.build.forceRecipeUsage', false) + set.config('latex.magic.args', ['--shell-escape']) await build(rootFile, 'latex', async () => {}) @@ -153,9 +152,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should do nothing but log an error with magic comments but disabled', async () => { - const rootFile = set.root(fixture, 'magic.tex') - await set.config('latex.recipes', []) - await set.config('latex.build.forceRecipeUsage', true) + const rootFile = set.root('magic.tex') + set.config('latex.recipes', []) + set.config('latex.build.forceRecipeUsage', true) await build(rootFile, 'latex', async () => {}) @@ -163,9 +162,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should skip undefined tools in the recipe and log an error', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.tools', [{ name: 'existingTool', command: 'pdflatex' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool', 'existingTool'] }]) + const rootFile = set.root('main.tex') + set.config('latex.tools', [{ name: 'existingTool', command: 'pdflatex' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool', 'existingTool'] }]) await build(rootFile, 'latex', async () => {}) @@ -178,9 +177,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should do nothing but log an error if no tools are prepared', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.tools', []) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool'] }]) + const rootFile = set.root('main.tex') + set.config('latex.tools', []) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool'] }]) await build(rootFile, 'latex', async () => {}) @@ -191,7 +190,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { describe('lw.compile->recipe.createOutputSubFolders', () => { beforeEach(() => { - getIncludedTeXStub.returns([ set.root(fixture, 'main.tex') ]) + getIncludedTeXStub.returns([ set.root('main.tex') ]) }) afterEach(() => { @@ -199,7 +198,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should resolve the output directory relative to the root directory if not absolute', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const relativeOutDir = 'output' const expectedOutDir = path.resolve(path.dirname(rootFile), relativeOutDir) getOutDirStub.returns(relativeOutDir) @@ -210,7 +209,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should use the absolute output directory as is', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const absoluteOutDir = '/absolute/output' getOutDirStub.returns(absoluteOutDir) @@ -220,7 +219,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should create the output directory if it does not exist', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const relativeOutDir = 'output' const expectedOutDir = path.resolve(path.dirname(rootFile), relativeOutDir) const stub = sinon.stub(lw.file, 'exists').resolves(false) @@ -234,7 +233,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not create the output directory if it already exists', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const relativeOutDir = 'output' const stub = sinon.stub(lw.file, 'exists').resolves({ type: vscode.FileType.Directory, ctime: 0, mtime: 0, size: 0 }) getOutDirStub.returns(relativeOutDir) @@ -257,8 +256,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { queue.clear() }) - beforeEach(async () => { - await set.config('latex.build.forceRecipeUsage', false) + beforeEach(() => { + set.config('latex.build.forceRecipeUsage', false) }) afterEach(() => { @@ -335,8 +334,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should detect only LW recipe comment', async () => { - await set.config('latex.tools', [{ name: 'Tool1', command: 'pdflatex' }, { name: 'Tool2', command: 'xelatex' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool2'] }]) + set.config('latex.tools', [{ name: 'Tool1', command: 'pdflatex' }, { name: 'Tool2', command: 'xelatex' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool2'] }]) readStub.resolves('% !LW recipe = Recipe2\n') @@ -405,10 +404,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { queue.clear() }) - beforeEach(async () => { - await set.config('latex.build.forceRecipeUsage', false) - await set.config('latex.magic.args', ['--shell-escape']) - await set.config('latex.magic.bib.args', ['--min-crossrefs=1000']) + beforeEach(() => { + set.config('latex.build.forceRecipeUsage', false) + set.config('latex.magic.args', ['--shell-escape']) + set.config('latex.magic.bib.args', ['--min-crossrefs=1000']) }) afterEach(() => { @@ -482,13 +481,13 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.compile->recipe.findRecipe', () => { - beforeEach(async () => { - await set.config('latex.tools', [{ name: 'Tool1', command: 'pdflatex' }, { name: 'Tool2', command: 'xelatex' }, { name: 'Tool3', command: 'lualatex' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool2'] }, { name: 'Recipe3', tools: ['Tool3'] }]) + beforeEach(() => { + set.config('latex.tools', [{ name: 'Tool1', command: 'pdflatex' }, { name: 'Tool2', command: 'xelatex' }, { name: 'Tool3', command: 'lualatex' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool2'] }, { name: 'Recipe3', tools: ['Tool3'] }]) }) it('should do nothing but log an error if no recipes are defined', async () => { - await set.config('latex.recipes', []) + set.config('latex.recipes', []) await build('dummy.tex', 'latex', async () => {}) @@ -496,7 +495,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should use the default recipe name if recipeName is undefined', async () => { - await set.config('latex.recipe.default', 'Recipe2') + set.config('latex.recipe.default', 'Recipe2') await build('dummy.tex', 'latex', async () => {}) @@ -512,7 +511,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should return the last used recipe if defaultRecipeName is `lastUsed`', async () => { - await set.config('latex.recipe.default', 'lastUsed') + set.config('latex.recipe.default', 'lastUsed') await build('dummy.tex', 'latex', async () => {}, 'Recipe2') queue.clear() @@ -523,8 +522,22 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.strictEqual(step.name, 'Tool2') }) + it('should use the updated new tools in the last used recipe if defaultRecipeName is `lastUsed`', async () => { + set.config('latex.recipe.default', 'lastUsed') + + await build('dummy.tex', 'latex', async () => {}, 'Recipe2') + queue.clear() + + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool3'] }, { name: 'Recipe3', tools: ['Tool3'] }]) + await build('dummy.tex', 'latex', async () => {}) + + const step = queue.getStep() + assert.ok(step) + assert.strictEqual(step.name, 'Tool3') + }) + it('should reset prevRecipe if the language ID changes', async () => { - await set.config('latex.recipe.default', 'lastUsed') + set.config('latex.recipe.default', 'lastUsed') await build('dummy.tex', 'latex', async () => {}, 'Recipe2') queue.clear() @@ -536,7 +549,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should return the first matching recipe based on langId if no recipe is found', async () => { - await set.config('latex.recipes', [ + set.config('latex.recipes', [ { name: 'recipe1', tools: [] }, { name: 'rsweave Recipe', tools: [] }, { name: 'weave.jl Recipe', tools: [] }, @@ -600,9 +613,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { extRoot = lw.extensionRoot }) - beforeEach(async () => { - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) + beforeEach(() => { + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) }) afterEach(() => { @@ -621,7 +634,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should modify command when Docker is enabled on Windows', async () => { - await set.config('docker.enabled', true) + set.config('docker.enabled', true) setPlatform('win32') lw.extensionRoot = '/path/to/extension' @@ -633,7 +646,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should modify command and chmod when Docker is enabled on non-Windows', async () => { - await set.config('docker.enabled', true) + set.config('docker.enabled', true) setPlatform('linux') lw.extensionRoot = '/path/to/extension' @@ -648,7 +661,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not modify command when Docker is disabled', async () => { - await set.config('docker.enabled', false) + set.config('docker.enabled', false) await build('dummy.tex', 'latex', async () => {}) @@ -658,8 +671,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should replace argument placeholders', async () => { - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk', args: ['%DOC%', '%DOC%', '%DIR%'], env: {} }]) - const rootFile = set.root(fixture, 'main.tex') + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk', args: ['%DOC%', '%DOC%', '%DIR%'], env: {} }]) + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) @@ -667,11 +680,11 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.ok(step) assert.pathStrictEqual(step.args?.[0], rootFile.replace('.tex', '')) assert.pathStrictEqual(step.args?.[1], rootFile.replace('.tex', '')) - assert.pathStrictEqual(step.args?.[2], get.path(fixture)) + assert.pathStrictEqual(step.args?.[2], get.path('')) }) it('should set TeX directories correctly', async () => { - await set.config('latex.tools', [ + set.config('latex.tools', [ { name: 'latexmk', command: 'latexmk', @@ -679,7 +692,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { env: {}, }, ]) - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const stub = sinon.stub(lw.file, 'setTeXDirs') await build(rootFile, 'latex', async () => {}) @@ -689,7 +702,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should process environment variables correctly', async () => { - await set.config('latex.tools', [ + set.config('latex.tools', [ { name: 'latexmk', command: 'latexmk', @@ -697,7 +710,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { env: { DOC: '%DOC%' }, }, ]) - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) @@ -707,10 +720,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should append max print line arguments when enabled', async () => { - await set.config('latex.option.maxPrintLine.enabled', true) - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.option.maxPrintLine.enabled', true) + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) syncStub.returns({ stdout: 'pdfTeX 3.14159265-2.6-1.40.21 (MiKTeX 2.9.7350 64-bit)' }) - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) @@ -718,7 +731,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.ok(step) assert.ok(step.args?.includes('--max-print-line=' + lw.constant.MAX_PRINT_LINE), step.args?.join(' ')) - await set.config('latex.tools', [{ name: 'latexmk', command: 'pdflatex' }]) + set.config('latex.tools', [{ name: 'latexmk', command: 'pdflatex' }]) initialize() await build(rootFile, 'latex', async () => {}) @@ -726,7 +739,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.ok(step) assert.ok(step.args?.includes('--max-print-line=' + lw.constant.MAX_PRINT_LINE), step.args?.join(' ')) - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk', args: ['--lualatex'] }]) + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk', args: ['--lualatex'] }]) initialize() await build(rootFile, 'latex', async () => {}) @@ -759,9 +772,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { syncStub = sinon.stub(lw.external, 'sync') }) - beforeEach(async () => { - await set.config('latex.option.maxPrintLine.enabled', true) - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + beforeEach(() => { + set.config('latex.option.maxPrintLine.enabled', true) + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) }) afterEach(() => { @@ -774,7 +787,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { it('should not consider MikTeX logic when pdflatex command fails', async () => { syncStub.throws(new Error('Command failed')) - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) @@ -785,7 +798,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not execute compile program again to determine MikTeX if already executed and cached', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') syncStub.returns({ stdout: 'pdfTeX 3.14159265-2.6-1.40.21 (MiKTeX 2.9.7350 64-bit)' }) await build(rootFile, 'latex', async () => {}) diff --git a/test/units/07_compile_external.test.ts b/test/units/07_compile_external.test.ts index 5b1fc9380..c1aab1ab2 100644 --- a/test/units/07_compile_external.test.ts +++ b/test/units/07_compile_external.test.ts @@ -9,10 +9,8 @@ import { build } from '../../src/compile/external' import type { ExternalStep } from '../../src/types' describe(path.basename(__filename).split('.')[0] + ':', () => { - const fixture = path.basename(__filename).split('.')[0] - before(() => { - mock.object(lw, 'file', 'root') + mock.init(lw, 'file', 'root') }) after(() => { @@ -33,7 +31,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should create a Tool object representing the build command and arguments', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build('command', ['arg1', 'arg2'], '/cwd', sinon.stub(), rootFile) @@ -67,7 +65,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const stub = sinon.stub().returnsArg(0) const replaceStub = sinon.stub(lwUtils, 'replaceArgumentPlaceholders').returns(stub) const pathStub = sinon.stub(lw.file, 'getPdfPath').returns('main.pdf') - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build('command', ['arg1', 'arg2'], '/cwd', sinon.stub(), rootFile) replaceStub.restore() @@ -92,11 +90,11 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should set the compiledPDFPath if a root file is provided', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build('command', ['arg1', 'arg2'], '/cwd', sinon.stub(), rootFile) - assert.pathStrictEqual(lw.compile.compiledPDFPath, get.path(fixture, 'main.pdf')) + assert.pathStrictEqual(lw.compile.compiledPDFPath, get.path('main.pdf')) }) it('should not set the compiledPDFPath if no root file is provided', async () => { @@ -114,7 +112,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should add the build tool to the queue for execution', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build('command', ['arg1', 'arg2'], '/cwd', sinon.stub(), rootFile) diff --git a/test/units/08_compile_build.test.ts b/test/units/08_compile_build.test.ts new file mode 100644 index 000000000..adf1e2e58 --- /dev/null +++ b/test/units/08_compile_build.test.ts @@ -0,0 +1,516 @@ +import * as vscode from 'vscode' +import type { SpawnOptions } from 'child_process' +import * as cs from 'cross-spawn' +import * as path from 'path' +import * as sinon from 'sinon' +import { assert, get, log, mock, set, sleep } from './utils' +import { lw } from '../../src/lw' +import { autoBuild, build } from '../../src/compile/build' +import * as pick from '../../src/utils/quick-pick' +import { terminate } from '../../src/compile/terminate' + +describe(path.basename(__filename).split('.')[0] + ':', () => { + let activeStub: sinon.SinonStub + let findStub: sinon.SinonStub + + before(() => { + mock.init(lw, 'file', 'root') + ;(lw.cache.getIncludedTeX as sinon.SinonStub).returns([get.path('main.tex')]) + findStub = sinon.stub(lw.root, 'find') + ;(lw.extra.clean as sinon.SinonStub).resolves(Promise.resolve()) + }) + + beforeEach(() => { + activeStub = mock.activeTextEditor(get.path('main.tex'), '', { languageId: 'latex' }) + findStub.callsFake(() => { + set.root('main.tex') + return Promise.resolve(undefined) + }) + set.config('latex.tools', [ + { name: 'tool', command: 'bash', args: ['-c', 'exit 0;'] }, + { name: 'bad', command: 'bash', args: ['-c', 'exit 1;'] }, + ]) + set.config('latex.recipes', [{ name: 'recipe', tools: ['tool'] }]) + }) + + afterEach(() => { + activeStub.restore() + findStub.resetHistory() + }) + + after(() => { + sinon.restore() + }) + + describe('lw.compile->build.build', () => { + it('should do nothing if there is no active text editor', async () => { + activeStub.restore() + + await build() + + assert.hasLog('Cannot start to build because the active editor is undefined.') + }) + + it('should try find root if not given as an argument', async () => { + await build() + + assert.ok(findStub.called) + }) + + it('should skip finding root if given as an argument', async () => { + await build(false, get.path('alt.tex'), 'latex') + + assert.ok(!findStub.called) + }) + + it('should use the correct root file if not given as an argument', async () => { + set.root('main.tex') + + await build() + + assert.hasLog(`Building root file: ${get.path('main.tex')}`) + }) + + it('should use external command to build project if set', async () => { + set.config('latex.external.build.command', 'bash') + set.config('latex.external.build.args', ['-c', 'exit 0;#external']) + + await build() + + assert.hasLog('Recipe step 1 The command is bash:["-c","exit 0;#external"].') + }) + + it('should use the current pwd as external command cwd', async () => { + set.config('latex.external.build.command', 'bash') + set.config('latex.external.build.args', ['-c', 'echo $PWD']) + + await build() + + assert.pathStrictEqual( + get.compiler.log().split('\n')[0].trim(), + path.dirname(get.path('main.tex')).replace(/^([a-zA-Z]):/, (_, p1: string) => '\\' + p1.toLowerCase()) + ) + }) + + it('should do nothing if cannot find root and not external', async () => { + findStub.callsFake(() => { + lw.root.file.path = undefined + lw.root.file.langId = undefined + return Promise.resolve(undefined) + }) + + await build() + + assert.hasLog('Cannot find LaTeX root file. See') + }) + + it('should let use pick root file when subfile is detected', async () => { + lw.root.subfiles.path = get.path('subfile.tex') + lw.root.file.langId = 'latex' + const stub = sinon.stub(pick, 'pickRootPath').resolves(get.path('subfile.tex')) + + await build() + + lw.root.subfiles.path = undefined + lw.root.file.langId = undefined + stub.restore() + + assert.hasLog(`Building root file: ${get.path('subfile.tex')}`) + }) + + it('should skip picking root file if `skipSelection` is `true`', async () => { + lw.root.subfiles.path = get.path('subfile.tex') + lw.root.file.langId = 'latex' + const stub = sinon.stub(pick, 'pickRootPath').resolves(get.path('subfile.tex')) + + await build(true) + + lw.root.subfiles.path = undefined + lw.root.file.langId = undefined + stub.restore() + + assert.hasLog(`Building root file: ${get.path('main.tex')}`) + }) + }) + + describe('lw.compile->build.buildLoop', () => { + it('should not loop a new build if another one is ongoing', async () => { + set.config('latex.tools', [{ name: 'tool', command: 'bash', args: ['-c', 'sleep .5;exit 0;'] }]) + + const buildPromise = build() + await build() + await buildPromise + + assert.hasLog('Another build loop is already running.') + }) + + it('should increment `lw.compile.compiledPDFWriting` to avoid PDF refresh during compilation', async () => { + await build() + + assert.ok(lw.compile.compiledPDFWriting > 0, lw.compile.compiledPDFWriting.toString()) + + await new Promise((resolve) => + setTimeout( + resolve, + (vscode.workspace.getConfiguration('latex-workshop').get('latex.watch.pdf.delay') as number) + 100 + ) + ) + + assert.strictEqual(lw.compile.compiledPDFWriting, 0) + }) + + it('should handle multiple steps one by one', async () => { + set.config('latex.recipes', [{ name: 'recipe', tools: ['tool', 'tool'] }]) + + await build() + + assert.hasLog('Recipe step 1 The command is bash:["-c","exit 0;"].') + assert.hasLog('Recipe step 2 The command is bash:["-c","exit 0;"].') + }) + + it('should early-terminate if a step returns non-zero code', async () => { + set.config('latex.recipes', [{ name: 'recipe', tools: ['bad', 'tool'] }]) + set.config('latex.autoBuild.cleanAndRetry.enabled', false) + + await build() + + assert.hasLog('Recipe step 1 The command is bash:["-c","exit 1;"].') + assert.notHasLog('Recipe step 2 The command is bash:["-c","exit 0;"].') + }) + + it('should correctly set `skipped` flag to `true` for skipped latexmk steps', async () => { + set.config('latex.tools', [ + { name: 'tool', command: 'bash', args: ['-c', 'echo "Latexmk: All targets (build) are up-to-date";'] }, + ]) + + const stub = lw.viewer.refresh as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(!stub.called) + + set.config('latex.tools', [{ name: 'tool', command: 'bash', args: ['-c', 'exit 0;'] }]) + + stub.resetHistory() + await build() + + assert.ok(stub.called) + }) + }) + + describe('lw.compile->build.spawnProcess', () => { + let readStub: sinon.SinonStub + + before(() => { + readStub = sinon.stub(lw.file, 'read') + }) + + after(() => { + readStub.restore() + }) + + + it('should respect `latex.build.clearLog.everyRecipeStep.enabled` config', async () => { + set.config('latex.tools', [{ name: 'tool', command: 'bash', args: ['-c', 'echo 1;'] }]) + set.config('latex.recipes', [{ name: 'recipe', tools: ['tool', 'tool'] }]) + set.config('latex.build.clearLog.everyRecipeStep.enabled', true) + + await build() + const stepLog = get.compiler.log() + + set.config('latex.build.clearLog.everyRecipeStep.enabled', false) + await build() + assert.strictEqual(stepLog + stepLog, get.compiler.log()) + }) + + it('should handle magic comment %!TEX program', async () => { + readStub.resolves('% !TEX program = echo\n') + set.config('latex.build.forceRecipeUsage', false) + set.config('latex.magic.args', ['--arg1', '--arg2']) + + await build() + assert.strictEqual(get.compiler.log().trim(), '--arg1 --arg2') + }) + + it('should handle magic comment % !TEX program with % !TEX options', async () => { + readStub.resolves('% !TEX program = echo\n% !TEX options = --arg1 --arg2\n') + set.config('latex.build.forceRecipeUsage', false) + + await build() + assert.strictEqual(get.compiler.log().trim(), '--arg1 --arg2') + }) + + it('should use the root file directory as cwd when building', async () => { + set.root('main.tex') + const spawnSpy = sinon.spy(lw.external, 'spawn') + + await build() + spawnSpy.restore() + + assert.pathStrictEqual(spawnSpy.getCall(0)?.args?.[2].cwd?.toString(), path.dirname(get.path('main.tex'))) + }) + + it('should change current working directory to root when using subfiles to compile sub files', async () => { + set.root('main.tex') + set.config('latex.tools', [ + { name: 'tool', command: 'latexmk', args: [] }, + ]) + lw.root.subfiles.path = get.path('sub/subfile.tex') + + const spawnStub = sinon.stub(lw.external, 'spawn') + let lastSpawnArgs: [command: string, args: readonly string[], options: SpawnOptions] | undefined + spawnStub.callsFake((...args) => { + lastSpawnArgs = args + return cs.spawn('true') + }) + + await build(true, get.path('sub/subfile.tex'), 'latex') + spawnStub.restore() + + lw.root.subfiles.path = undefined + + assert.pathStrictEqual(lastSpawnArgs?.[2].cwd?.toString(), path.dirname(get.path('main.tex'))) + }) + + it('should set and use `max_print_line` envvar when building', async () => { + const spawnSpy = sinon.spy(lw.external, 'spawn') + + await build() + spawnSpy.restore() + + assert.strictEqual(spawnSpy.getCall(0)?.args?.[2].env?.['max_print_line'], lw.constant.MAX_PRINT_LINE) + }) + + it('should spawn external commands with no env', async () => { + set.config('latex.external.build.command', 'echo') + const spawnSpy = sinon.spy(lw.external, 'spawn') + + await build() + spawnSpy.restore() + + assert.strictEqual(spawnSpy.getCall(0)?.args?.[2].env, undefined) + }) + }) + + describe('lw.compile->build.monitorProcess', () => { + it('should handle process error', async () => { + set.config('latex.tools', [{ name: 'tool', command: 'absolutely-nonexistent', args: [] }]) + + await build() + + assert.hasLog('LaTeX fatal error on PID') + assert.hasLog('Error: spawn absolutely-nonexistent ENOENT') + }) + + it('should stop the recipe on process error', async () => { + set.config('latex.tools', [{ name: 'tool', command: 'absolutely-nonexistent', args: [] }]) + set.config('latex.recipes', [{ name: 'recipe', tools: ['tool', 'tool'] }]) + + await build() + + assert.notHasLog('Recipe step 2') + }) + + it('should retry building on non-zero exit code and `latex.autoBuild.cleanAndRetry.enabled`', async () => { + set.config('latex.autoBuild.cleanAndRetry.enabled', true) + set.config('latex.autoClean.run', 'onFailed') + set.config('latex.recipes', [{ name: 'recipe', tools: ['bad'] }]) + + ;(lw.extra.clean as sinon.SinonStub).resetHistory() + await build() + + // First build + assert.hasLog('Cleaning auxiliary files and retrying build after toolchain error.') + // Second build + assert.strictEqual((lw.extra.clean as sinon.SinonStub).callCount, 2) + }) + + it('should handle external command failure', async () => { + set.config('latex.external.build.command', 'bash') + set.config('latex.external.build.args', ['-c', 'exit 1;#external']) + + await build() + + assert.hasLog('Build with external command returns error') + }) + + it('should silently omit user termination', async () => { + set.config('latex.autoBuild.cleanAndRetry.enabled', false) + set.config('latex.tools', [{ name: 'tool', command: 'bash', args: ['-c', 'sleep 10;exit 0;'] }]) + + ;(lw.extra.clean as sinon.SinonStub).resetHistory() + const promise = build() + let spawned = false + while (!spawned) { + try { + assert.hasLog('Recipe step 1 The command is bash:["-c","sleep 10;exit 0;"].') + spawned = true + } catch (_) { + await sleep(10) + } + } + terminate() + await promise + + assert.notHasLog('Cleaning auxiliary files and retrying build after toolchain error.') + assert.notHasLog('Build with external command returns error') + assert.strictEqual((lw.extra.clean as sinon.SinonStub).callCount, 0) + }) + }) + + describe('lw.compile->build.afterSuccessfulBuilt', () => { + it('should refresh the viewer after successful external command build, nothing else', async () => { + set.config('latex.external.build.command', 'bash') + set.config('latex.external.build.args', ['-c', 'exit 0;#external']) + set.root('main.tex') + + const stub = lw.viewer.refresh as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(stub.called) + assert.notHasLog(`Successfully built ${get.path('main.tex')}`) + }) + + it('should not refresh viewer if the build is a skipped latexmk one', async () => { + set.config('latex.tools', [ + { name: 'tool', command: 'bash', args: ['-c', 'echo "Latexmk: All targets (build) are up-to-date";'] }, + ]) + + const stub = lw.viewer.refresh as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(!stub.called) + }) + + it('should refresh viewer on general successful builds', async () => { + const stub = lw.viewer.refresh as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(stub.called) + }) + + it('should call `lw.completion.reference.setNumbersFromAuxFile` to set reference numbers', async () => { + const stub = lw.completion.reference.setNumbersFromAuxFile as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(stub.called) + }) + + it('should load generated .fls file in cache', async () => { + const stub = lw.cache.loadFlsFile as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(stub.called) + }) + + it('should call syncTeX only if the viewer is in `external` mode', async () => { + set.config('view.pdf.viewer', 'external') + set.config('synctex.afterBuild.enabled', true) + + await build() + + assert.hasLog('SyncTex after build invoked.') + }) + + it('should not call syncTeX if the viewer is not in `external` mode', async () => { + set.config('view.pdf.viewer', 'tab') + set.config('synctex.afterBuild.enabled', true) + + await build() + + assert.notHasLog('SyncTex after build invoked.') + }) + + it('should not call syncTeX if `synctex.afterBuild.enabled` is false', async () => { + set.config('view.pdf.viewer', 'external') + set.config('synctex.afterBuild.enabled', false) + + await build() + + assert.notHasLog('SyncTex after build invoked.') + }) + + it('should auto-clean if `latex.autoClean.run` is `onSucceeded` or `onBuilt`', async () => { + set.config('latex.autoClean.run', 'onSucceeded') + ;(lw.extra.clean as sinon.SinonStub).resetHistory() + await build() + assert.strictEqual((lw.extra.clean as sinon.SinonStub).callCount, 1) + + set.config('latex.autoClean.run', 'onBuilt') + ;(lw.extra.clean as sinon.SinonStub).resetHistory() + await build() + assert.strictEqual((lw.extra.clean as sinon.SinonStub).callCount, 1) + }) + }) + + describe('lw.compile->build.autoBuild', () => { + beforeEach(() => { + lw.compile.lastAutoBuildTime = 0 + }) + + it('should not trigger auto-build if invoking event is not set in config', async () => { + set.config('latex.autoBuild.run', 'onFileChange') + log.start() + await autoBuild(get.path('main.tex'), 'onSave') + log.stop() + assert.notHasLog('Auto build started') + + set.config('latex.autoBuild.run', 'onSave') + log.start() + await autoBuild(get.path('main.tex'), 'onFileChange') + log.stop() + assert.notHasLog('Auto build started') + }) + + it('should not trigger auto-build if the last auto-build is too soon', async () => { + set.config('latex.autoBuild.run', 'onFileChange') + set.config('latex.autoBuild.delay', 10000) + + await autoBuild(get.path('main.tex'), 'onFileChange') + log.start() + await autoBuild(get.path('main.tex'), 'onFileChange') + log.stop() + + assert.hasLog('Auto build started') + assert.hasLog('Autobuild temporarily disabled.') + + lw.compile.lastAutoBuildTime = 0 + log.start() + await autoBuild(get.path('main.tex'), 'onFileChange') + log.stop() + + assert.notHasLog('Autobuild temporarily disabled.') + }) + + it('should auto-build subfiles if `latex.rootFile.useSubFile` is true and no bib change is detected', async () => { + set.config('latex.autoBuild.run', 'onFileChange') + set.config('latex.rootFile.useSubFile', true) + set.root('main.tex') + lw.root.subfiles.path = get.path('subfile.tex') + lw.root.subfiles.langId = 'latex' + + log.start() + await autoBuild(get.path('subfile.tex'), 'onFileChange', false) + log.stop() + + lw.root.subfiles.path = undefined + lw.root.subfiles.langId = undefined + + assert.hasLog(`Building root file: ${get.path('subfile.tex')}`) + assert.notHasLog(`Building root file: ${get.path('main.tex')}`) + + lw.compile.lastAutoBuildTime = 0 + log.start() + await autoBuild(get.path('subfile.tex'), 'onFileChange', false) + log.stop() + + assert.hasLog(`Building root file: ${get.path('main.tex')}`) + assert.notHasLog(`Building root file: ${get.path('subfile.tex')}`) + }) + }) +}) diff --git a/test/units/utils.ts b/test/units/utils.ts index 207e750fe..a531e31ab 100644 --- a/test/units/utils.ts +++ b/test/units/utils.ts @@ -12,7 +12,8 @@ type ExtendedAssert = typeof nodeAssert & { pathStrictEqual: (actual: string | undefined, expected: string | undefined, message?: string | Error) => void, pathNotStrictEqual: (actual: string | undefined, expected: string | undefined, message?: string | Error) => void, hasLog: (message: string | RegExp) => void, - notHasLog: (message: string | RegExp) => void + notHasLog: (message: string | RegExp) => void, + hasCompilerLog: (message: string | RegExp) => void } export const assert: ExtendedAssert = nodeAssert as ExtendedAssert assert.listStrictEqual = (actual: T[] | undefined, expected: T[] | undefined, message?: string | Error) => { @@ -32,21 +33,31 @@ function getPaths(actual: string | undefined, expected: string | undefined): [st return [actual, expected] } assert.pathStrictEqual = (actual: string | undefined, expected: string | undefined, message?: string | Error) => { - assert.strictEqual(path.relative(...getPaths(actual, expected)), '', message) + [actual, expected] = getPaths(actual, expected) + assert.strictEqual(path.relative(actual, expected), '', message ?? `Paths are not equal: ${actual} !== ${expected} .`) } assert.pathNotStrictEqual = (actual: string | undefined, expected: string | undefined, message?: string | Error) => { - assert.notStrictEqual(path.relative(...getPaths(actual, expected)), '', message) + [actual, expected] = getPaths(actual, expected) + assert.notStrictEqual(path.relative(actual, expected), '', message ?? `Paths are equal: ${actual} === ${expected} .`) } function hasLog(message: string | RegExp) { return typeof message === 'string' ? log.all().some(logMessage => logMessage.includes(lwLog.applyPlaceholders(message))) : log.all().some(logMessage => message.exec(logMessage)) } +function hasCompilerLog(message: string | RegExp) { + return typeof message === 'string' + ? lwLog.getCachedLog().CACHED_COMPILER.some(logMessage => logMessage.includes(message)) + : lwLog.getCachedLog().CACHED_COMPILER.some(logMessage => message.exec(logMessage)) +} assert.hasLog = (message: string | RegExp) => { - assert.ok(hasLog(message), log.all().join('\n')) + assert.ok(hasLog(message), '\n' + log.all().join('\n')) } assert.notHasLog = (message: string | RegExp) => { - assert.ok(!hasLog(message), log.all().join('\n')) + assert.ok(!hasLog(message), '\n' + log.all().join('\n')) +} +assert.hasCompilerLog = (message: string | RegExp) => { + assert.ok(hasCompilerLog(message), '\n' + lwLog.getCachedLog().CACHED_COMPILER.join('\n')) } export const get = { @@ -60,20 +71,28 @@ export const get = { } else { return result } + }, + compiler: { + log: () => lwLog.getCachedLog().CACHED_COMPILER.join('') } } +const configs: Map = new Map() const changedConfigs: Set = new Set() export const set = { root: (...paths: string[]) => { const rootFile = get.path(...paths) lw.root.file.path = rootFile + lw.root.file.langId = 'latex' lw.root.dir.path = path.dirname(rootFile) return rootFile }, - config: async (section: string, value: any) => { - await vscode.workspace.getConfiguration('latex-workshop').update(section, value) + config: (section: string, value: any) => { + configs.set(section, value) + }, + codeConfig: async (section: string, value: any) => { changedConfigs.add(section) + await vscode.workspace.getConfiguration('latex-workshop').update(section, value) } } @@ -87,9 +106,10 @@ export const reset = { }, config: async () => { for (const section of changedConfigs.values()) { - await set.config(section, undefined) + await set.codeConfig(section, undefined) } changedConfigs.clear() + configs.clear() }, log: () => { lwLog.resetCachedLog() @@ -117,6 +137,10 @@ export function sleep(ms: number) { } export const mock = { + init: (obj: any, ...ignore: string[]) => { + mock.object(obj, ...ignore) + mock.config() + }, object: (obj: any, ...ignore: string[]) => { const items = Object.getPrototypeOf(obj) === Object.prototype ? Object.getOwnPropertyNames(obj) @@ -133,6 +157,25 @@ export const mock = { } }) }, + config: () => { + const original = vscode.workspace.getConfiguration + sinon.stub(vscode.workspace, 'getConfiguration').callsFake((section?: string, scope?: vscode.ConfigurationScope | null) => { + function getConfig(configName: string): T | undefined + function getConfig(configName: string, defaultValue: T): T + function getConfig(configName: string, defaultValue?: T): T | undefined { + if (configs.has(configName)) { + return configs.get(configName) as T + } + return originalConfig.get(configName, defaultValue) + } + const originalConfig = original(section, scope) + const configItem: vscode.WorkspaceConfiguration = { + ...originalConfig, + get: getConfig + } + return configItem + }) + }, textDocument: (filePath: string, content: string, params: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string } = {}) => { return sinon.stub(vscode.workspace, 'textDocuments').value([ new TextDocument(filePath, content, params) ]) },