Skip to content

Commit

Permalink
Validate css folders (#68)
Browse files Browse the repository at this point in the history
* add validation of css subfolder structure

* add tests for validation of css folders
  • Loading branch information
alexkilgour authored May 28, 2021
1 parent 115e4ad commit 532918b
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 42 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ The following example would allow a folder with the name `js` that contains file

#### CSSDirectoryStructure

This option allows you to specify a custom CSS folder structure. This is used in the [package creation](#package-creation) step to generate a sub-folder structure within a specified folder, to assist in quickly spinning up a new package. It is not used in the [validation](#package-validation) or [publication](#package-publication) steps.
This option allows you to specify a custom CSS folder structure. This is used in the [package creation](#package-creation) step to generate a sub-folder structure within a specified folder, to assist in quickly spinning up a new package. It is also used in the [validation](#package-validation) step to make sure that only valid CSS subdirectory naming is used.

The following shows an example folder structure, taken from the [Springer Nature Front-End Toolkits](https://github.com/springernature/frontend-toolkits) repository:

Expand Down
66 changes: 38 additions & 28 deletions __mocks__/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ const defaultPackageContents = {...{
'.adotfile': 'file content'
}, ...defaultFolders};

const brandPackageContents = {
brandA: {
'fileA.ext': 'file content here'
},
brandB: {
'fileB.ext': 'file content here'
}
};

const cssFolderPackageContents = {
'required.md': 'file content',
folder1: {
a: {
'fileA.scss': 'file content here'
},
b: {
'fileB.scss': 'file content here'
}
}
};

const __fsMockFiles = () => {
return {
'packages/package/pass': defaultPackageContents,
Expand All @@ -52,41 +73,30 @@ const __fsMockFiles = () => {
'packages/package/failIsFileType': defaultPackageContents,
'packages/package/failIsTopLevelFile': defaultPackageContents,
'path/to/global-package': defaultPackageContents,
'path/to/global-package-b': {
'some-file.txt': 'file content here',
'empty-dir': {/** empty directory */}
},
'path/to/global-package-b': {'some-file.txt': 'file content here', 'empty-dir': {/** empty directory */}},
'path/to/some.png': Buffer.from([8, 6, 7, 5, 3, 0, 9]),
'some/other/path': {/** another empty directory */},
'home/user/.npmrc': '//mock-registry.npmjs.org/:_authToken=xyz',
'home/user-b/.npmrc': `//registry.npmjs.org/:_authToken=$\{NPM_TOKEN}`,
'home/user-c/.npmrc': `//registry.npmjs.org/:_authToken=$\{OTHER_NPM_TOKEN}`,
'context/brand-context': {
brandA: {
'fileA.ext': 'file content here'
},
brandB: {
'fileB.ext': 'file content here'
}
},
'context/brand-context-disallowed': {
brandA: {
'fileA.ext': 'file content here'
},
brandB: {
'fileB.ext': 'file content here'
},
brandC: {
'fileB.ext': 'file content here'
'context/brand-context': brandPackageContents,
'context/brand-context-disallowed': {...brandPackageContents, ...{brandC: {'fileB.ext': 'file content here'}}},
'context/brand-context-empty': {/** empty directory */},
'valid-context/brand-context': brandPackageContents,
'packages/package/passWithCss': {
...cssFolderPackageContents,
...{
folder1: {
...cssFolderPackageContents.folder1, ...{c: {'fileC.css': 'file content here'}}
}
}
},
'context/brand-context-empty': {/** empty directory */},
'valid-context/brand-context': {
brandA: {
'fileA.ext': 'file content here'
},
brandB: {
'fileB.ext': 'file content here'
'packages/package/failIsCssFolder': {
...cssFolderPackageContents,
...{
folder1: {
...cssFolderPackageContents.folder1, ...{d: {'fileD.css': 'file content here'}}
}
}
}
};
Expand Down
20 changes: 20 additions & 0 deletions __mocks__/glob-results.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,26 @@ const packageFiles = {
'packages/package/passContextWithReadme/brandA/folder2/file.json',
'packages/package/passContextWithReadme/brandA/folder2/subfolder',
'packages/package/passContextWithReadme/brandA/folder2/subfolder/file.js'
],
'packages/package/passWithCss/**/*': [
'packages/package/passWithCss/required.md',
'packages/package/passWithCss/folder1',
'packages/package/passWithCss/folder1/a',
'packages/package/passWithCss/folder1/b',
'packages/package/passWithCss/folder1/c',
'packages/package/passWithCss/folder1/a/fileA.scss',
'packages/package/passWithCss/folder1/b/fileB.scss',
'packages/package/passWithCss/folder1/c/fileC.css'
],
'packages/package/failIsCssFolder/**/*': [
'packages/package/failIsCssFolder/required.md',
'packages/package/failIsCssFolder/folder1',
'packages/package/failIsCssFolder/folder1/a',
'packages/package/failIsCssFolder/folder1/b',
'packages/package/failIsCssFolder/folder1/d',
'packages/package/failIsCssFolder/folder1/a/fileA.scss',
'packages/package/failIsCssFolder/folder1/b/fileB.scss',
'packages/package/failIsCssFolder/folder1/d/fileD.css'
]
};

Expand Down
24 changes: 24 additions & 0 deletions __tests__/unit/_validate/check-package-structure.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ const validationConfigNoFolders = {
required: ['required.md']
};

const validationConfigWithCss = {
required: ['required.md'],
folders: {
folder1: ['scss', 'css']
},
CSSDirectoryStructure: {
folder1: ['a', 'b', 'c']
}
};

describe('Check validation', () => {
beforeEach(() => {
mockfs(MOCK_PACKAGES);
Expand Down Expand Up @@ -142,4 +152,18 @@ describe('Check validation', () => {
checkValidation(validationConfigWithChangelog, 'packages/package/passContextWithReadme', ['brandA'])
).resolves.toEqual();
});

test('Resolves when filesystem matches validationConfigWithCss', async () => {
expect.assertions(1);
await expect(
checkValidation(validationConfigWithCss, 'packages/package/passWithCss')
).resolves.toEqual();
});

test('Rejects when invalid folder within CSS configuration', async () => {
expect.assertions(1);
await expect(
checkValidation(validationConfigWithCss, 'packages/package/failIsCssFolder')
).rejects.toThrowError(new Error('Invalid files or folders in failIsCssFolder'));
});
});
71 changes: 58 additions & 13 deletions lib/js/_validate/_check-package-structure.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,43 @@ function isRequired(relativeFilePath) {
return required;
}

/**
* Check for valid paths within CSS folder structure
* @private
* @function isValidCssFolderPath
* @param {String} relativeFilePath relative file/folder name
* @param {String} topLevelFolder parent folder name
* @param {Object} cssDirectoryStructure CSS directory structure
* @return {Boolean}
*/
function isValidCssFolderPath(relativeFilePath, topLevelFolder, cssDirectoryStructure) {
for (const folder of cssDirectoryStructure[topLevelFolder]) {
const validCssPath = path.join(topLevelFolder, folder);

if (relativeFilePath.startsWith(validCssPath)) {
return true;
}
}

return false;
}

/**
* Check if glob item is a valid folder name
* If configFolders not set in config, all folders valid
* @private
* @function isFolder
* @param {String} filePath full path to item
* @param {String} relativeFilePath relative file/folder name
* @param {Object} cssDirectoryStructure CSS directory structure
* @param {String} brand optional name of the context brand
* @return {Boolean}
*/
function isFolder(filePath, relativeFilePath, brand) {
function isFolder(filePath, relativeFilePath, cssDirectoryStructure, brand) {
const relativeFileName = (brand) ? `${brand}/${relativeFilePath}` : relativeFilePath;
const splitGlob = relativeFilePath.split(path.sep);
const topLevelFolder = splitGlob[0];
let isValid = false;

// This is not a directory
if (!fs.lstatSync(filePath).isDirectory()) {
Expand All @@ -107,8 +132,14 @@ function isFolder(filePath, relativeFilePath, brand) {
return true;
}

const splitGlob = relativeFilePath.split(path.sep);
const isValid = configFolders.includes(splitGlob[0]);
// Valid CSS subfolder or valid topLevelFolder
isValid = (
cssDirectoryStructure && // CSS folder structure is set
topLevelFolder in cssDirectoryStructure && // This is a CSS folder
splitGlob.length > 1 // This is not the topLevelFolder CSS folder
) ?
isValidCssFolderPath(relativeFilePath, topLevelFolder, cssDirectoryStructure) :
configFolders.includes(topLevelFolder);

if (isValid) {
reporter.success('validating', relativeFileName, 'is a valid folder');
Expand All @@ -126,12 +157,16 @@ function isFolder(filePath, relativeFilePath, brand) {
* @private
* @function isFileType
* @param {String} relativeFilePath relative file/folder name
* @param {Object} cssDirectoryStructure CSS directory structure
* @param {String} brand optional name of the context brand
* @return {Boolean}
*/
function isFileType(relativeFilePath, brand) {
function isFileType(relativeFilePath, cssDirectoryStructure, brand) {
const relativeFileName = (brand) ? `${brand}/${relativeFilePath}` : relativeFilePath;
const splitGlob = relativeFilePath.split(path.sep);
const topLevelFolder = splitGlob[0];
const fileType = path.extname(relativeFilePath).slice(1);
let isValid = false;

// This is a top level file
if (splitGlob.length === 1) {
Expand All @@ -143,12 +178,18 @@ function isFileType(relativeFilePath, brand) {
return true;
}

const topLevelFolder = splitGlob[0];
const fileType = path.extname(relativeFilePath).slice(1);

// Is a valid extension within a valid folder?
if (configFolders.includes(topLevelFolder)) {
const isValid = config.folders[topLevelFolder].includes(fileType);
const isValidFileType = configFolders.includes(topLevelFolder) && config.folders[topLevelFolder].includes(fileType);

// Valid file in CSS subfolder or valid filetype within topLevelFolder
isValid = (
cssDirectoryStructure && // CSS folder structure is set
topLevelFolder in cssDirectoryStructure && // This is a CSS folder
splitGlob.length > 2 // not a file within top level CSS folder
) ?
isValidCssFolderPath(relativeFilePath, topLevelFolder, cssDirectoryStructure) && isValidFileType :
isValidFileType;

if (!isValid) {
reporter.fail('validating', relativeFileName, 'is not a valid file');
Expand Down Expand Up @@ -207,10 +248,11 @@ function removeNonValidatedPaths(filePaths) {
* @function checkPackageStructure
* @param {String} pathToPackage package path on filesystem
* @param {Object} globSettings configuration for glob search
* @param {Object} cssDirectoryStructure CSS directory structure
* @param {String} brand optional name of the context brand
* @return {Promise}
*/
async function checkPackageStructure(pathToPackage, globSettings, brand) {
async function checkPackageStructure(pathToPackage, globSettings, cssDirectoryStructure, brand) {
try {
const filePaths = await globby(globSettings.pattern, globSettings.options);
const pathsToValidate = removeNonValidatedPaths(filePaths);
Expand All @@ -224,8 +266,8 @@ async function checkPackageStructure(pathToPackage, globSettings, brand) {
if (
!isRequired(relativeFilePath) &&
!isBrandReadme(brand, relativeFileName) &&
!isFolder(filePath, relativeFilePath, brand) &&
!isFileType(relativeFilePath, brand)
!isFolder(filePath, relativeFilePath, cssDirectoryStructure, brand) &&
!isFileType(relativeFilePath, cssDirectoryStructure, brand)
) {
// If not recongnised at any other step then invalid top level file
reporter.fail('validating', relativeFileName, 'is not a valid top level file');
Expand Down Expand Up @@ -271,7 +313,8 @@ async function init(validationConfig, pathToPackage, configuredBrands) {
{
pattern: `${pathToPackage}/*`,
options: {onlyFiles: true}
}
},
config.CSSDirectoryStructure
);

// Validate files within each brand
Expand All @@ -282,6 +325,7 @@ async function init(validationConfig, pathToPackage, configuredBrands) {
pattern: `${pathToPackage}/${brand}/**/*`,
options: {onlyFiles: false}
},
config.CSSDirectoryStructure,
brand
);
}
Expand All @@ -292,7 +336,8 @@ async function init(validationConfig, pathToPackage, configuredBrands) {
{
pattern: `${pathToPackage}/**/*`,
options: {onlyFiles: false}
}
},
config.CSSDirectoryStructure
);
}

Expand Down

0 comments on commit 532918b

Please sign in to comment.