diff --git a/__tests__/spec/update-script.js b/__tests__/spec/update-script.js index 9a881d28e7..5f750a65e6 100644 --- a/__tests__/spec/update-script.js +++ b/__tests__/spec/update-script.js @@ -540,6 +540,13 @@ describe('update.sh', () => { ' D app/assets/javascripts/step-by-step-nav.js' ])) }) + + it('updates app template includes', () => { + expect(gitStatus).toEqual(expect.arrayContaining([ + ' M app/views/includes/head.html', + ' M app/views/includes/scripts.html' + ])) + }) }) it('can be run as a piped script', async () => { diff --git a/lib/_update/post.js b/lib/_update/post.js index 521e06803f..6de7f65330 100644 --- a/lib/_update/post.js +++ b/lib/_update/post.js @@ -1,5 +1,7 @@ const { updateJavascripts } = require('./update-javascripts') const { updateScss } = require('./update-scss') +const { updateIncludes } = require('./update-includes') updateJavascripts() updateScss() +updateIncludes() diff --git a/lib/_update/update-includes.js b/lib/_update/update-includes.js new file mode 100644 index 0000000000..7d962581a4 --- /dev/null +++ b/lib/_update/update-includes.js @@ -0,0 +1,19 @@ +const { getProjectVersion, patchUserFile } = require('./util') + +async function updateIncludes () { + const userVersion = await getProjectVersion() + + // If the user already has version 13 or greater of the kit installed then + // their application.js file is all their code and we don't don't want to + // change it + if (userVersion >= '13.0.0') { + return + } + + await patchUserFile(userVersion, 'app/views/includes/head.html') + await patchUserFile(userVersion, 'app/views/includes/scripts.html') +} + +module.exports = { + updateIncludes +} diff --git a/lib/_update/update-includes.test.js b/lib/_update/update-includes.test.js new file mode 100644 index 0000000000..387197921b --- /dev/null +++ b/lib/_update/update-includes.test.js @@ -0,0 +1,202 @@ +/* eslint-env jest */ + +const fs = require('fs') +const fsp = require('fs').promises +const path = require('path') + +jest.mock('./util/fetch-original') +jest.mock('./util', () => { + const originalModule = jest.requireActual('./util') + + return { + ...originalModule, + getProjectVersion: jest.fn(async () => '') + } +}) +const { fetchOriginal: mockFetchOriginal } = require('./util/fetch-original') +const { getProjectVersion: mockGetProjectVersion } = require('./util') +const { projectDir } = require('../path-utils') + +const { updateIncludes } = require('./update-includes') + +const originalIncludesHead = ` + + +{% for stylesheetUrl in extensionConfig.stylesheets %} + +{% endfor %} +` + +const newIncludesHead = fs.readFileSync( + path.join(projectDir, 'app', 'views', 'includes', 'head.html'), + 'utf8' +) + +const originalIncludesScripts = ` + + +{% for scriptUrl in extensionConfig.scripts %} + +{% endfor %} + + + +{% if useAutoStoreData %} + +{% endif %} +` + +const newIncludesScripts = fs.readFileSync( + path.join(projectDir, 'app', 'views', 'includes', 'scripts.html'), + 'utf8' +) + +describe('updateIncludes', () => { + let mockCopyFile, mockReadFile, mockWriteFile + + beforeEach(() => { + mockGetProjectVersion.mockResolvedValue( + '12.1.1' + ) + + mockFetchOriginal.mockRejectedValue( + new Error('fetchOriginal called more times than expected') + ) + + mockCopyFile = jest.spyOn(fsp, 'copyFile').mockImplementation(async () => {}) + mockReadFile = jest.spyOn(fsp, 'readFile').mockResolvedValue(async () => {}) + mockWriteFile = jest.spyOn(fsp, 'writeFile').mockImplementation(async () => {}) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('replaces app/views/includes/{head.html, scripts.html} if the user has not updated them', async () => { + // original head.html + mockFetchOriginal.mockResolvedValueOnce( + originalIncludesHead + ) + // their head.html + mockReadFile.mockResolvedValueOnce( + originalIncludesHead + ) + // our head.html + mockReadFile.mockResolvedValueOnce( + newIncludesHead + ) + + // original scripts.html + mockFetchOriginal.mockResolvedValueOnce( + originalIncludesScripts + ) + // their scripts.html + mockReadFile.mockResolvedValueOnce( + originalIncludesScripts + ) + // our scripts.html + mockReadFile.mockResolvedValueOnce( + newIncludesScripts + ) + + await updateIncludes() + + expect(mockCopyFile).toHaveBeenCalledWith( + path.join(projectDir, 'update', 'app', 'views', 'includes', 'head.html'), + path.join(projectDir, 'app', 'views', 'includes', 'head.html') + ) + + expect(mockCopyFile).toHaveBeenCalledWith( + path.join(projectDir, 'update', 'app', 'views', 'includes', 'scripts.html'), + path.join(projectDir, 'app', 'views', 'includes', 'scripts.html') + ) + }) + + it('rewrites app/views/includes/{head.html, scripts.html} if the user has added lines to the bottom of the file', async () => { + // original head.html + mockFetchOriginal.mockResolvedValueOnce( + originalIncludesHead + ) + // their head.html + mockReadFile.mockResolvedValueOnce( + originalIncludesHead + '\n\n' + ) + // our head.html + mockReadFile.mockResolvedValueOnce( + newIncludesHead + ) + + // original scripts.html + mockFetchOriginal.mockResolvedValueOnce( + originalIncludesScripts + ) + // their scripts.html + mockReadFile.mockResolvedValueOnce( + originalIncludesScripts + '\n\n' + ) + // our scripts.html + mockReadFile.mockResolvedValueOnce( + newIncludesScripts + ) + + await updateIncludes() + + expect(mockWriteFile).toHaveBeenCalledWith( + path.join(projectDir, 'app', 'views', 'includes', 'head.html'), + newIncludesHead + '\n\n', + 'utf8' + ) + + expect(mockWriteFile).toHaveBeenCalledWith( + path.join(projectDir, 'app', 'views', 'includes', 'scripts.html'), + newIncludesScripts + '\n\n', + 'utf8' + ) + }) + + it('rewrites app/views/includes/scripts.html even if it cannot rewrite head.html', async () => { + // original head.html + mockFetchOriginal.mockResolvedValueOnce( + originalIncludesHead + ) + // their head.html + mockReadFile.mockResolvedValueOnce( + '\n\n' + ) + // our head.html + mockReadFile.mockResolvedValueOnce( + newIncludesHead + ) + + // original scripts.html + mockFetchOriginal.mockResolvedValueOnce( + originalIncludesScripts + ) + // their scripts.html + mockReadFile.mockResolvedValueOnce( + originalIncludesScripts + '\n\n' + ) + // our scripts.html + mockReadFile.mockResolvedValueOnce( + newIncludesScripts + ) + + const mockConsoleWarn = jest.spyOn(global.console, 'warn').mockImplementation(() => {}) + + await updateIncludes() + + expect(mockWriteFile).not.toHaveBeenCalledWith( + path.join(projectDir, 'app', 'views', 'includes', 'head.html'), + newIncludesHead + '\n\n', + 'utf8' + ) + + expect(mockWriteFile).toHaveBeenCalledWith( + path.join(projectDir, 'app', 'views', 'includes', 'scripts.html'), + newIncludesScripts + '\n\n', + 'utf8' + ) + + expect(mockConsoleWarn).toHaveBeenCalledTimes(1) + }) +})