replace-in-file
Version:
A simple utility to quickly replace text in one or more files.
564 lines (512 loc) • 16.1 kB
JavaScript
import {expect, use, should} from 'chai'
import chaiAsPromised from 'chai-as-promised'
import {processFile, processFileSync} from './process-file.js'
import fsAsync from 'node:fs/promises'
import fs from 'node:fs'
//Enable should assertion style for usage with chai-as-promised
should()
use(chaiAsPromised)
/**
* Specifications
*/
describe('Process a file', () => {
//Test JSON
const testData = 'a re place c'
/**
* Prepare test files
*/
beforeEach(() => Promise.all([
fsAsync.writeFile('test1', testData, 'utf8'),
fsAsync.writeFile('test2', testData, 'utf8'),
fsAsync.writeFile('test3', 'nope', 'utf8'),
]))
/**
* Clean up test files
*/
afterEach(() => Promise.all([
fsAsync.unlink('test1'),
fsAsync.unlink('test2'),
fsAsync.unlink('test3'),
]))
function fromToToProcessor(config) {
const from = config.from
const to = config.to
delete config.from
delete config.to
config.processor = (content) => {
return content.replace(from, to)
}
return config
}
function appendFileProcessor(config) {
config.processor = (content, file) => {
return `${content}${file}`
}
return config
}
/**
* Async
*/
describe('Async', () => {
it('should run processor', done => {
processFile({
files: 'test1',
processor: (input) => {
return input.replace(/re\splace/g, 'b')
},
}).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal(testData)
done()
})
})
it('should run processorAsync', done => {
processFile({
files: 'test1',
processorAsync: async (input) => {
const replaceValue = await Promise.resolve('b')
return input.replace(/re\splace/g, replaceValue)
},
}).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal(testData)
done()
})
})
it('should replace contents in a single file with regex', done => {
processFile(fromToToProcessor({
files: 'test1',
from: /re\splace/g,
to: 'b',
})).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal(testData)
done()
})
})
it('should replace contents with a string replacement', done => {
processFile(fromToToProcessor({
files: 'test1',
from: 're place',
to: 'b',
})).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
expect(test1).to.equal('a b c')
done()
})
})
it('should replace contents in a an array of files', done => {
processFile(fromToToProcessor({
files: ['test1', 'test2'],
from: /re\splace/g,
to: 'b',
})).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal('a b c')
done()
})
})
it('should expand globs', done => {
processFile(fromToToProcessor({
files: 'test*',
from: /re\splace/g,
to: 'b',
})).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal('a b c')
done()
})
})
it('should expand globs while excluding ignored files', done => {
processFile(fromToToProcessor({
files: 'test*',
ignore: 'test1',
from: /re\splace/g,
to: 'b',
})).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a re place c')
expect(test2).to.equal('a b c')
done()
})
})
it('should replace substrings', done => {
processFile(fromToToProcessor({
files: 'test1',
from: /(re)\s(place)/g,
to: '$2 $1',
})).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
expect(test1).to.equal('a place re c')
done()
})
})
it('should fulfill the promise on success', () => {
return processFile(fromToToProcessor({
files: 'test1',
from: /re\splace/g,
to: 'b',
})).should.be.fulfilled
})
it('should reject the promise with an error on failure', () => {
return expect(processFile(fromToToProcessor({
files: 'nope',
from: /re\splace/g,
to: 'b',
}))).to.eventually.be.rejectedWith(Error)
})
it('should not reject the promise if allowEmptyPaths is true', () => {
return processFile(fromToToProcessor({
files: 'nope',
allowEmptyPaths: true,
from: /re\splace/g,
to: 'b',
})).should.be.fulfilled
})
it('should return a results array', done => {
processFile(fromToToProcessor({
files: 'test1',
from: /re\splace/g,
to: 'b',
})).then(results => {
expect(results).to.be.instanceof(Array)
expect(results).to.have.length(1)
expect(results[0].file).to.equal('test1')
done()
})
})
it('should mark if something was replaced', done => {
processFile(fromToToProcessor({
files: 'test1',
from: /re\splace/g,
to: 'b',
})).then(results => {
expect(results[0].hasChanged).to.equal(true)
done()
})
})
it('should not mark if nothing was replaced', done => {
processFile(fromToToProcessor({
files: 'test1',
from: 'nope',
to: 'b',
})).then(results => {
expect(results[0].hasChanged).to.equal(false)
done()
})
})
it('should return correct results for multiple files', done => {
processFile(fromToToProcessor({
files: ['test1', 'test2', 'test3'],
from: /re\splace/g,
to: 'b',
})).then(results => {
expect(results).to.have.length(3)
expect(results[0].file).to.equal('test1')
expect(results[0].hasChanged).to.equal(true)
expect(results[1].file).to.equal('test2')
expect(results[1].hasChanged).to.equal(true)
expect(results[2].file).to.equal('test3')
expect(results[2].hasChanged).to.equal(false)
done()
})
})
it('should not replace in a dry run', done => {
processFile(fromToToProcessor({
files: ['test1', 'test2'],
from: /re\splace/g,
to: 'b',
dry: true,
})).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a re place c')
expect(test2).to.equal('a re place c')
done()
})
})
it('should return changed files for a dry run', done => {
processFile(fromToToProcessor({
files: ['test1', 'test2', 'test3'],
from: /re\splace/g,
to: 'b',
dry: true,
})).then(results => {
expect(results).to.have.length(3)
expect(results[0].file).to.equal('test1')
expect(results[0].hasChanged).to.equal(true)
expect(results[1].file).to.equal('test2')
expect(results[1].hasChanged).to.equal(true)
expect(results[2].file).to.equal('test3')
expect(results[2].hasChanged).to.equal(false)
done()
})
})
it('should accept glob configuration', done => {
processFile(fromToToProcessor({
files: 'test1',
from: /re\splace/g,
to: 'b',
allowEmptyPaths: true,
glob: {
ignore: ['test1'],
},
})).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
expect(test1).to.equal('a re place c')
done()
})
})
it('should ignore empty glob configuration', done => {
processFile(fromToToProcessor({
files: 'test1',
from: /re\splace/g,
to: 'b',
glob: null,
})).then(() => {
const test1 = fs.readFileSync('test1', 'utf8')
expect(test1).to.equal('a b c')
done()
})
})
describe('fs', () => {
it('reads and writes using a custom fs when provided', done => {
const before = 'abc'
let written
const fs = {
readFile: async () => {
return before
},
writeFile: async (_fileName, data) => {
written = data
},
}
processFile({
files: 'test1',
fs,
processor: (input) => {
return input.replace(/b/, 'z')
},
}).then(() => {
expect(written).to.equal('azc')
done()
})
})
})
})
/**
* Sync
*/
describe('Sync', () => {
it('should replace contents in a single file with regex', function() {
processFileSync(fromToToProcessor({
files: 'test1',
from: /re\splace/g,
to: 'b',
}))
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal(testData)
})
it('should replace contents with a string replacement', function() {
processFileSync(fromToToProcessor({
files: 'test1',
from: 're place',
to: 'b',
}))
const test1 = fs.readFileSync('test1', 'utf8')
expect(test1).to.equal('a b c')
})
it('should replace contents in a an array of files', function() {
processFileSync(fromToToProcessor({
files: ['test1', 'test2'],
from: /re\splace/g,
to: 'b',
}))
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal('a b c')
})
it('should expand globs', function() {
processFileSync(fromToToProcessor({
files: 'test*',
from: /re\splace/g,
to: 'b',
}))
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal('a b c')
})
it('should return a results array', function() {
const results = processFileSync(fromToToProcessor({
files: 'test1',
from: /re\splace/g,
to: 'b',
}))
expect(results).to.be.instanceof(Array)
expect(results).to.have.length(1)
expect(results[0].file).to.equal('test1')
})
it('should mark if something was replaced', function() {
const results = processFileSync(fromToToProcessor({
files: 'test1',
from: /re\splace/g,
to: 'b',
}))
expect(results[0].hasChanged).to.equal(true)
})
it('should not mark if nothing was replaced', function() {
const results = processFileSync(fromToToProcessor({
files: 'test1',
from: 'nope',
to: 'b',
}))
expect(results[0].hasChanged).to.equal(false)
})
it('should return correct results for multiple files', function() {
const results = processFileSync(fromToToProcessor({
files: ['test1', 'test2', 'test3'],
from: /re\splace/g,
to: 'b',
}))
expect(results).to.have.length(3)
expect(results[0].file).to.equal('test1')
expect(results[0].hasChanged).to.equal(true)
expect(results[1].file).to.equal('test2')
expect(results[1].hasChanged).to.equal(true)
expect(results[2].file).to.equal('test3')
expect(results[2].hasChanged).to.equal(false)
})
it('should expand globs while excluding ignored files', () => {
processFileSync(fromToToProcessor({
files: 'test*',
ignore: 'test1',
from: /re\splace/g,
to: 'b',
}))
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a re place c')
expect(test2).to.equal('a b c')
})
it('should support an array of ignored files', () => {
processFileSync(fromToToProcessor({
files: 'test*',
ignore: ['test1', 'test3'],
from: /re\splace/g,
to: 'b',
}))
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a re place c')
expect(test2).to.equal('a b c')
})
it('should not fail when the ignore parameter is undefined', () => {
processFileSync(fromToToProcessor({
files: 'test*',
ignore: undefined,
from: /re\splace/g,
to: 'b',
}))
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal('a b c')
})
it('should work without expanding globs if disabled', () => {
processFileSync(fromToToProcessor({
files: ['test1', 'test2'],
from: /re\splace/g,
to: 'b',
disableGlobs: true,
}))
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a b c')
expect(test2).to.equal('a b c')
})
it('should not replace in a dry run', () => {
processFileSync(fromToToProcessor({
files: ['test1', 'test2'],
from: /re\splace/g,
to: 'b',
dry: true,
}))
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
expect(test1).to.equal('a re place c')
expect(test2).to.equal('a re place c')
})
it('should return changed files for a dry run', () => {
const results = processFileSync(fromToToProcessor({
files: ['test1', 'test2', 'test3'],
from: /re\splace/g,
to: 'b',
dry: true,
}))
expect(results).to.have.length(3)
expect(results[0].file).to.equal('test1')
expect(results[0].hasChanged).to.equal(true)
expect(results[1].file).to.equal('test2')
expect(results[1].hasChanged).to.equal(true)
expect(results[2].file).to.equal('test3')
expect(results[2].hasChanged).to.equal(false)
})
it('should pass filename to processor as second parameter', () => {
processFileSync(appendFileProcessor({
files: 'test*',
}))
const test1 = fs.readFileSync('test1', 'utf8')
const test2 = fs.readFileSync('test2', 'utf8')
const test3 = fs.readFileSync('test3', 'utf8')
expect(test1).to.equal(`${testData}test1`)
expect(test2).to.equal(`${testData}test2`)
expect(test3).to.equal('nopetest3')
})
describe('fsSync', () => {
it('reads and writes using a custom fsSync when provided', done => {
const before = 'a'
let written
const fsSync = {
readFileSync: () => {
return before
},
writeFileSync: (_fileName, data) => {
written = data
return data
},
}
const results = processFileSync({
files: 'test1',
fsSync,
processor: (input) => {
return input.replace(/a/, 'z')
},
})
expect(results[0].file).to.equal('test1')
expect(written).to.equal('z')
done()
})
})
})
describe('module export', () => {
it(`exports named processFile and processFileSync from module`, () => {
expect(processFile).to.be.a('function')
expect(processFileSync).to.be.a('function')
})
})
})