Skip to content

Commit

Permalink
Added accordion and accordion-item components (#66)
Browse files Browse the repository at this point in the history
# Pull Request

## 📖 Description

<!--- Provide some background and a description of your work. -->
This change adds the following:
- Ability to add required components in foundation components so that sub-components can be installed
- Exports errors as functions so that arguments can be added to them for more precise error messaging
- Added the accordion component
- Added the accordion-item component

### 🎫 Issues

<!---
List and link relevant issues here using the keyword "closes"
if this PR will close an issue, eg. closes #411
-->
- Continued work on #31

## ✅ Checklist

### General

<!--- Review the list and put an x in the boxes that apply. -->

- [x] I have added tests for my changes.
- [x] I have tested my changes.
- [x] I have updated the project documentation to reflect my changes.

## ⏭ Next Steps

<!---
If there is relevant follow-up work to this PR, please list any existing issues or provide brief descriptions of what you would like to do next.
-->
- Continue adding foundation components
  • Loading branch information
janechu committed Jun 2, 2022
1 parent fb7a816 commit c2f5dba
Show file tree
Hide file tree
Showing 31 changed files with 757 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "Added accordion and accordion-item components",
"packageName": "@microsoft/fast-cli",
"email": "7559015+janechu@users.noreply.github.com",
"dependentChangeType": "none"
}
19 changes: 15 additions & 4 deletions packages/fast-cli/src/cli.errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
export const fastConfigDoesNotExistErrorMessage = "fastconfig.json file does not exist, run the config command to generate this file."
export const fastConfigDoesNotContainComponentPathMessage = "fastconfig.json file does not contain a component path, add a component path to generate a design system file.";
export const componentTemplateFilesNotFoundMessage = "The component template provided does not appear to contain any files or could not be read.";
export const componentTemplateFileNotFoundMessage = "The component template provided is missing the following required template file";
export function fastConfigDoesNotExistErrorMessage(): string {
return "fastconfig.json file does not exist, run the config command to generate this file.";
}
export function fastConfigDoesNotContainComponentPathMessage(): string {
return "fastconfig.json file does not contain a component path, add a component path to generate a design system file.";
}
export function componentTemplateFilesNotFoundMessage(): string {
return "The component template provided does not appear to contain any files or could not be read.";
}
export function componentTemplateFileNotFoundMessage(): string {
return "The component template provided is missing the following required template file";
}
export function fastAddComponentRequiredComponentMissingNameModificatierMessage(name: string): string {
return `The ${name} has a required component that does not supply a prepend or append modification`
}
13 changes: 12 additions & 1 deletion packages/fast-cli/src/cli.options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,19 @@ export interface PackageJsonAddComponent {
peerDependencies: { [key: string]: string };
}

export interface RequiredComponentsNameModifierConfig {
prepend: string;
append: string;
}

export interface RequiredComponents {
template: string;
nameModifier: RequiredComponentsNameModifierConfig
}

export interface FastAddComponent {
packageJson: PackageJsonAddComponent
packageJson: PackageJsonAddComponent;
requiredComponents?: Array<RequiredComponents>;
}

export interface FastInitOptionMessages {
Expand Down
48 changes: 40 additions & 8 deletions packages/fast-cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import * as commander from "commander";
import prompts from "prompts";
import spawn from "cross-spawn";
import fs from "fs-extra";
import type { AddComponentOptionMessages, AddComponentOptions, AddDesignSystemOptionMessages, AddDesignSystemOptions, AddFoundationComponentOptionMessages, AddFoundationComponentOptions, FastAddComponent, FastConfig, FastConfigOptionMessages, FastInit, FastInitOptionMessages, PackageJsonAddComponent, PackageJsonInit } from "./cli.options.js";
import type { AddComponentOptionMessages, AddComponentOptions, AddDesignSystemOptionMessages, AddDesignSystemOptions, AddFoundationComponentOptionMessages, AddFoundationComponentOptions, FastAddComponent, FastConfig, FastConfigOptionMessages, FastInit, FastInitOptionMessages, PackageJsonAddComponent, PackageJsonInit, RequiredComponents, RequiredComponentsNameModifierConfig } from "./cli.options.js";
import { requiredComponentTemplateFiles } from "./components/files.js";
import { componentTemplateFileNotFoundMessage, componentTemplateFilesNotFoundMessage, fastConfigDoesNotContainComponentPathMessage, fastConfigDoesNotExistErrorMessage } from "./cli.errors.js";
import { componentTemplateFileNotFoundMessage, componentTemplateFilesNotFoundMessage, fastAddComponentRequiredComponentMissingNameModificatierMessage, fastConfigDoesNotContainComponentPathMessage, fastConfigDoesNotExistErrorMessage } from "./cli.errors.js";
import type { XOR } from "./cli.types.js";
import type { ComponentTemplateConfig } from "./utilities/template.js";
import { availableTemplates } from "./components/options.js";
import { suggestedTemplates } from "./components/options.js";

const __dirname = path.resolve(path.dirname(""));
const program = new commander.Command();
Expand Down Expand Up @@ -290,13 +290,13 @@ async function getFastConfig(): Promise<FastConfig> {
const fastConfigPath = path.resolve(process.cwd(), "fast.config.json");

if (!await fs.pathExists(fastConfigPath)) {
throw new Error(fastConfigDoesNotExistErrorMessage);
throw new Error(fastConfigDoesNotExistErrorMessage());
}

const fastConfig = JSON.parse(fs.readFileSync(fastConfigPath, { encoding: "utf8" }));

if (typeof fastConfig.componentPath !== "string") {
throw new Error(fastConfigDoesNotContainComponentPathMessage);
throw new Error(fastConfigDoesNotContainComponentPathMessage());
}

return fastConfig;
Expand Down Expand Up @@ -410,7 +410,7 @@ async function checkTemplateForFiles(pathToTemplatePackage: string): Promise<voi
const directoryContents = fs.readdirSync(templateDir);

if (!Array.isArray(directoryContents)) {
throw new Error(componentTemplateFilesNotFoundMessage);
throw new Error(componentTemplateFilesNotFoundMessage());
}

// Run through available template files and make sure all required files are accounted for
Expand Down Expand Up @@ -512,6 +512,27 @@ async function addComponent(
);
}

function modifyName(
name: string,
modifierConfig: RequiredComponentsNameModifierConfig
): string {
let updatedName = name;

if (modifierConfig.append) {
updatedName = updatedName + modifierConfig.append;
}

if (modifierConfig.prepend) {
updatedName = modifierConfig.prepend + updatedName;
}

if (name === updatedName) {
throw new Error(fastAddComponentRequiredComponentMissingNameModificatierMessage(name));
}

return updatedName;
}

/**
* Add a foundation component
*/
Expand All @@ -527,9 +548,9 @@ async function addFoundationComponent(
type: "autocomplete",
name: "template",
message: messages.template,
choices: availableTemplates.map((availableTemplate) => {
choices: suggestedTemplates.map((suggestedTemplate) => {
return {
title: availableTemplate
title: suggestedTemplate
};
})
}
Expand All @@ -555,6 +576,17 @@ async function addFoundationComponent(
`dist/esm/components/${options.template}`
)
);

if (Array.isArray(fastAddComponent.requiredComponents)) {
fastAddComponent.requiredComponents.forEach((requiredComponent: RequiredComponents) => {
addFoundationComponent({
...options,
name: modifyName(config.name as string, requiredComponent.nameModifier),
template: requiredComponent.template,
}, messages);
});
}

await installEnumeratedDependencies(
Object.entries(fastAddComponent?.packageJson?.dependencies || {}).map(([key, value]: [string, string]): string => {
return `${key}@${value}`;
Expand Down
3 changes: 3 additions & 0 deletions packages/fast-cli/src/components/accordion-item/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Description

This template is used as an accordion-item component template from which a user can modify without having to do the initial scaffolding.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { expect, test } from "@playwright/test";
import { execSync } from "child_process";
import {
expectedGeneratedComponentTemplateFiles,
setupComponent,
teardown,
getGeneratedComponentFiles,
getTempDir,
getTempComponentDir,
} from "../../test/helpers.js";

const uuid: string = "accordion-item";
const tempDir: string = getTempDir(uuid);
const tempComponentDir: string = getTempComponentDir(uuid);

test.describe(`CLI add-foundation-component ${uuid}`, () => {
test.beforeAll(() => {
setupComponent(uuid, tempDir, tempComponentDir);
});
test.afterAll(() => {
teardown(tempDir, tempComponentDir);
});
test("should copy files from the template", () => {
expect(getGeneratedComponentFiles(tempDir)).toEqual(expectedGeneratedComponentTemplateFiles);
});
test("should be able to run the build", () => {
expect(
() => {
execSync(`cd ${tempDir} && npm run build`);
}
).not.toThrow();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ComponentTemplateConfig } from "../../../utilities/template";

export default (config: ComponentTemplateConfig): string => `
# ${config.className}
The ${config.tagName} component is used in conjunction with ${config.tagName}.
For more information on the building blocks used to create this component, please refer to https://www.fast.design/docs/components/accordion`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { ComponentTemplateConfig } from "../../../utilities/template";

export default (config: ComponentTemplateConfig): string =>
`import { template } from "./${config.tagName}.template.js";
import { styles } from "./${config.tagName}.styles.js";
export const definition = {
baseName: "${config.tagName}",
template,
styles,
collapsedIcon: /* html */ \`
<svg
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.22 3H3.78a.78.78 0 00-.78.78v12.44c0 .43.35.78.78.78h12.44c.43 0 .78-.35.78-.78V3.78a.78.78 0 00-.78-.78zM3.78 2h12.44C17.2 2 18 2.8 18 3.78v12.44c0 .98-.8 1.78-1.78 1.78H3.78C2.8 18 2 17.2 2 16.22V3.78C2 2.8 2.8 2 3.78 2zM11 9h3v2h-3v3H9v-3H6V9h3V6h2v3z"
/>
</svg>
\`,
expandedIcon: /* html */ \`
<svg
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.78 3h12.44c.43 0 .78.35.78.78v12.44c0 .43-.35.78-.78.78H3.78a.78.78 0 01-.78-.78V3.78c0-.43.35-.78.78-.78zm12.44-1H3.78C2.8 2 2 2.8 2 3.78v12.44C2 17.2 2.8 18 3.78 18h12.44c.98 0 1.78-.8 1.78-1.78V3.78C18 2.8 17.2 2 16.22 2zM14 9H6v2h8V9z"
/>
</svg>
\`,
};`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { ComponentTemplateConfig } from "../../../utilities/template";

export default (config: ComponentTemplateConfig): string => `
import { expect, test } from "@playwright/test";
import { fixtureURL } from "@microsoft/fast-cli/dist/esm/utilities/playwright.js";
test.describe("${config.tagName}", () => {
const fixture = fixtureURL("${config.tagName}");
test.beforeEach(async ({ page }) => {
await page.goto(fixture);
});
test("should load the fixture URL", async ({ page }) => {
const pageUrl = page.url();
expect(pageUrl).toBe(\`http://localhost:3000/\${fixture}\`);
});
test("should contain the component in the URL", async ({ page }) => {
const element = page.locator("${config.tagName}");
await expect(element).not.toBeNull();
});
});
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { ComponentTemplateConfig } from "../../../utilities/template";

export default (config: ComponentTemplateConfig): string => `
import Template from "./fixtures/base.html";
import "./define.js";
export default {
title: "${config.tagName}",
};
export const ${config.className}: () => "*.html" = () => Template;
`;

0 comments on commit c2f5dba

Please sign in to comment.