Merge branch 'master' of github.com:Microsoft/frontend-bootcamp

This commit is contained in:
Ken
2019-02-28 12:03:19 -08:00
27 changed files with 200 additions and 140 deletions

View File

@@ -5,7 +5,7 @@
</head>
<body>
<header>
<h1>todos</h1>
<h1>todos - step1-02 exercise</h1>
<div class="addTodo">
<input class="textfield" placeholder="add todo" />
<button class="submit">Add</button>

View File

@@ -5,7 +5,7 @@
</head>
<body>
<header>
<h1>todos</h1>
<h1>todos - step1-02 final</h1>
<div class="addTodo">
<input class="textfield" placeholder="add todo" />
<button class="submit">Add</button>

View File

@@ -5,7 +5,7 @@
</head>
<body>
<header>
<h1>todos</h1>
<h1>todos - step1-03 demo</h1>
<div class="addTodo">
<input class="textfield" placeholder="add todo" />
<button class="submit">Add</button>

View File

@@ -5,7 +5,7 @@
</head>
<body>
<header>
<h1>todos</h1>
<h1>todos - step1-03 exercise</h1>
<div class="addTodo">
<input class="textfield" placeholder="add todo" />
<button onclick="addTodo()" class="submit">Add</button>

View File

@@ -5,7 +5,7 @@
</head>
<body>
<header>
<h1>todos</h1>
<h1>todos - step1-03 final</h1>
<div class="addTodo">
<input class="textfield" placeholder="add todo" />
<button onclick="addTodo()" class="submit">Add</button>

View File

@@ -4,7 +4,7 @@ export class TodoHeader extends React.Component {
render() {
return (
<header>
<h1>todos</h1>
<h1>todos - step1-05 exercise</h1>
<div className="addTodo">
<input className="textfield" placeholder="add todo" />
<button className="submit">Add</button>

View File

@@ -4,7 +4,7 @@ export class TodoHeader extends React.Component {
render() {
return (
<header>
<h1>todos</h1>
<h1>todos - step1-05 final</h1>
<div className="addTodo">
<input className="textfield" placeholder="add todo" />
<button className="submit">Add</button>

View File

@@ -10,7 +10,7 @@ export class TodoHeader extends React.Component<any, any> {
const { filter } = this.props;
return (
<header>
<h1>todos</h1>
<h1>todos - step1-06 demo</h1>
<div className="addTodo">
<input className="textfield" placeholder="add todo" />
<button className="submit">Add</button>

View File

@@ -11,7 +11,7 @@ export class TodoHeader extends React.Component<any, any> {
return (
<header>
<h1>todos</h1>
<h1>todos - step1-06 exercise</h1>
<div className="addTodo">
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
<button className="submit">Add</button>

View File

@@ -11,7 +11,7 @@ export class TodoHeader extends React.Component<any, any> {
return (
<header>
<h1>todos</h1>
<h1>todos - step1-06 final</h1>
<div className="addTodo">
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
<button className="submit">Add</button>

View File

@@ -11,7 +11,7 @@ export class TodoHeader extends React.Component<any, any> {
const { filter } = this.props;
return (
<header>
<h1>todos</h1>
<h1>todos - step1-07 demo</h1>
<div className="addTodo">
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
<button className="submit">Add</button>

View File

@@ -11,7 +11,7 @@ export class TodoHeader extends React.Component<any, any> {
const { filter } = this.props;
return (
<header>
<h1>todos</h1>
<h1>todos - step1-07 exercise</h1>
<div className="addTodo">
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
<button className="submit">Add</button>

View File

@@ -21,7 +21,7 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
const { filter, setFilter } = this.props;
return (
<header>
<h1>todos</h1>
<h1>todos - step1-07 final</h1>
<div className="addTodo">
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
<button onClick={this._onAdd} className="submit">

View File

@@ -22,7 +22,7 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
return (
<Stack>
<Stack horizontal horizontalAlign="center">
<Text variant="xxLarge">todos</Text>
<Text variant="xxLarge">todos - step2-02 demo</Text>
</Stack>
<Stack horizontal>

View File

@@ -5,7 +5,7 @@ export class TodoHeader extends React.Component<{}, {}> {
render() {
return (
<div>
<h1>todos</h1>
<h1>todos - step2-02 exercise</h1>
<input className="textfield" placeholder="add todo" />
<button className="button add">Add</button>
<div className="filter">

View File

@@ -1,32 +1,48 @@
# Step 2.3: Theming and Styling
# Step 2.3: Theming and styling with UI Fabric
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
Theming and Styling with UI Fabric. In this section, we will illustrate how to utilize some of the built-in theming and styling features right inside UI Fabric component library.
In this section, we will illustrate how to use some of the built-in theming and styling features of the UI Fabric component library.
For advanced or non-Fabric component scenarios, UI Fabric also exposes its own CSS-in-JS library called `mergeStyles` that is very performant compared with other similar libraries. A CodePen that illustrates what `mergeStyles` does: https://codepen.io/dzearing/pen/jGdgrE?editors=1011
These are the theming and styling methods that we will focus on in this step:
These are the areas that we will focus on in this step:
1. Theming with Fabric using `<Customizer>` component
1. Theming using the `<Customizer>` component
2. Customizing themes and loading with `loadTheme()`
3. Customizing Fabric Components `styles` prop
4. CSS-in-JS with mergeStyles
3. Customizing Fabric components via the `styles` prop
4. CSS-in-JS with `mergeStyles`
## Fabric Theming and Styling
The first three methods only work with Fabric components, but the fourth, `mergeStyles`, can be used in other projects as well (and is typically not used within Fabric-based projects).
### 1. Applying Fabric Themes
## 1. Applying Fabric themes using `<Customizer>`
- Fabric applies themes by propagating the theme down the children through the React Context mechanism
- It is applied with the `<Customizer>` component
- There are some predefined themes within Fabric already, like Fluent (which will become the default in the next major), MDL2, Azure, and some other sample themes like Teams.
- Take a look at `demo/src/components/TodoApp.tsx`
One way to apply a theme is by wrapping the components to be themed with a `<Customizer>` component. `Customizer` propagates the theme down to children through the [React Context](https://reactjs.org/docs/context.html) mechanism.
### 2. Customizing Fabric Themes
There are some predefined themes within Fabric already, like Fluent (which will become the default in the next major release), MDL2, Azure, and some other sample themes like Teams.
- Use the `loadTheme()` function to load a theme (applies to entire application):
- Erase the `<Customizer>` inside the `TodoApp.tsx` and place this code in the module scope. This will initialize a theme to be used throughout the application
- Fabric website has a handy theme generator to get you started with a theme: https://developer.microsoft.com/en-us/fabric#/styles/themegenerator
The following code (simplified from `demo/src/components/TodoApp.tsx`) shows an example of applying the Fluent theme to our todo app using `Customizer`.
```tsx
import { Customizer } from 'office-ui-fabric-react';
import { FluentCustomizations } from '@uifabric/fluent-theme';
function render() {
return (
<Customizer {...FluentCustomizations}>
<Stack>
<TodoHeader />
<TodoList />
<TodoFooter />
</Stack>
</Customizer>
);
}
```
## 2. Applying customized themes using `loadTheme()`
Another way to apply a theme is using the `loadTheme()` function. Themes loaded this way apply to the entire application.
To try out `loadTheme()` in our todo app, remove the `<Customizer>` tag from `TodoApp.tsx` and place this code in the module scope.
```ts
import { loadTheme } from 'office-ui-fabric-react';
@@ -59,28 +75,89 @@ loadTheme({
});
```
### 3. Customizing One Fabric Control Instance
> If you'd like to create your own theme, the Fabric website has a [handy theme generator](https://developer.microsoft.com/en-us/fabric#/styles/themegenerator) to help get you started.
- Fabric components expose a `styles` prop (not to be confused with the React built-in one called `style`)
- You can use intellisense to discover which parts of the component you can to customize
- You can even use a style function to change the style based on some style prop
- Take a look at these customizations in `demo/src/components/TodoHeader.tsx`
## 3. Customizing one Fabric control instance
### 4. CSS-in-JS with mergeStyles
If you just want to customize a single component instance's styling, Fabric components expose a `styles` prop (not to be confused with the React built-in one called `style`).
- `mergeStyles` is a styling library that creates CSS class from styles that are expressed in JS.
- Fabric uses `mergeStyles` under the hood, so typically you would only directly use `mergeStyles` in niche or non-Fabric scenarios.
- These classes can be passed into `className` prop of any component like `<div>`
- This library replaces the need to import CSS stylesheets because they are bundled as normal JS code
- Take a look at `demo/src/components/TodoApp.tsx`
You can use intellisense to discover which parts of the component you can to customize.
The `styles` prop can take either an object, or a function which returns a style object based on the component's prop values.
The following code (simplified from `demo/src/components/TodoHeader.tsx`) demonstrates using `styles` to customize individual components. The TextField uses a style function and the PrimaryButton uses a style object.
```tsx
function render() {
return (
<Stack>
<Stack.Item>
<TextField
placeholder="What needs to be done?"
styles={(props: ITextFieldStyleProps): Partial<ITextFieldStyles> => ({
...(props.focused && {
field: {
backgroundColor: '#c7e0f4'
}
})
})}
/>
</Stack.Item>
<PrimaryButton styles={{
root: { backgroundColor: 'maroon' },
rootHovered: { background: 'green' }
}}>
Add
</PrimaryButton>
</Stack>
);
}
```
## 4. CSS-in-JS with `mergeStyles`
`mergeStyles` is a styling library that creates CSS class names from styles that are expressed as JavaScript objects. These classes can be used as the `className` prop of any component or element, such as `<div>`.
This is an advanced approach which also works outside of Fabric. Within Fabric-based apps, you would typically only use `mergeStyles` in certain niche scenarios. (Fabric itself uses `mergeStyles` under the hood to power some of its styling.)
Benefits of `mergeStyles` include:
- Works in any app
- Eliminates the need to import or bundle CSS stylesheets (all styles are bundled as normal JS code)
- Provides type checking for styles (like Sass) when used with TypeScript
- Very performant compared with other similar libraries
The following is a basic example using mergeStyles. ([This CodePen](https://codepen.io/dzearing/pen/jGdgrE?editors=1011) illustrates in more detail what `mergeStyles` does and includes some advanced examples.)
```tsx
// can also import from office-ui-fabric-react in Fabric-based apps
import { mergeStyles } from '@uifabric/merge-styles';
const blueBackgroundClassName = mergeStyles({
backgroundColor: 'green'
});
const className = mergeStyles(blueBackgroundClassName, {
padding: 50, // px is assumed if no units are given
selectors: {
':hover': {
backgroundColor: 'red'
}
}
});
const myDiv = (
<div className={className}>
I am a green div that turns red on hover!
</div>
);
```
# Exercises
## Fabric Theming and Styling
## Fabric theming and styling
### Applying Fabric Themes
### Applying Fabric themes
Apply some included and predefined themes from the UI Fabric package inside the `/step2-03/exercise/src/components/TodoApp.tsx`. Do this by replacing:
Try applying some predefined themes from UI Fabric packages inside the TodoApp under `exercise/src/components/TodoApp.tsx`. Do this by replacing:
```ts
import { FluentCustomizations } from '@uifabric/fluent-theme';
@@ -92,18 +169,17 @@ with:
import { TeamsCustomizations } from '@uifabric/theme-samples';
```
### Customizing Fabric Themes
### Applying customized themes
Create your own theme and apply the color palette here:
https://developer.microsoft.com/en-us/fabric#/styles/themegenerator
1. Create your own theme using the [theme generator](https://developer.microsoft.com/en-us/fabric#/styles/themegenerator) and copy the generated code.
1. Delete the `Customizer` component
2. In `exercise/src/components/TodoApp.tsx`, delete the `Customizer` component.
2. Paste in this code in the `TodoApp.tsx` before the `TodoApp` component definition
3. Paste in the generated theme code before the `TodoApp` component definition.
3. Play around with the values and use intellisense to discover the `ITheme` type within VS Code
4. Play around with the values and use VS Code's intellisense to discover more properties of the `ITheme` type.
### Customizing One Fabric Control Instance
### Customizing one Fabric control instance
1. Open `exercise/src/components/TodoFooter.tsx`
@@ -113,15 +189,11 @@ https://developer.microsoft.com/en-us/fabric#/styles/themegenerator
4. Try to customize this with a styles function
## Advanced / Non-Fabric Component Styling
## Advanced/non-Fabric component styling
### CSS in JS with MergeStyles
### CSS-in-JS with `mergeStyles`
The styling library name is neither glamorous nor does it bring about emotion, but it is very quick and lightweight. `MergeStyles` turns CSS Rules into CSS class names to be applied to the components.
**NOTE:** Fabric components automatically use `mergeStyles` under the hood, so it is typically not necessary to directly call `mergeStyles` when styling Fabric components.
1. Try applying a merged style `className` as a prop inside `TodoApp`
1. Try generating a class name using `mergeStyles` and use it as a `className` prop inside `TodoApp`
```tsx
import { mergeStyles } from 'office-ui-fabric-react';

View File

@@ -22,7 +22,7 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
return (
<Stack gap={10}>
<Stack horizontal horizontalAlign="center">
<Text variant="xxLarge">todos</Text>
<Text variant="xxLarge">todos - step2-03 demo</Text>
</Stack>
<Stack horizontal gap={10}>

View File

@@ -22,7 +22,7 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
return (
<Stack gap={10}>
<Stack horizontal horizontalAlign="center">
<Text variant="xxLarge">todos</Text>
<Text variant="xxLarge">todos - step2-03 exercise</Text>
</Stack>
<Stack horizontal gap={10}>

View File

@@ -1,28 +1,27 @@
# Step 2.4
# Step 2.4: Testing TypeScript code with Jest
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
Testing TypeScript code with jest. jest is a test framework made by Facebook and is very popular in the React and the wider JS ecosystem. We will work on implementing simple unit tests here in this exercise.
[Jest](https://jestjs.io/) is a test framework made by Facebook and is very popular in the React and wider JS ecosystems.
https://jestjs.io/
In this exercise, we will work on implementing simple unit tests using Jest.
# jest Features
## Jest Features
- Multi-threaded and isolated test runner
- Provides a "fake" browser environment if needed (window, document, DOM, etc).
- Snapshots: show API or large object changes along side code changes in pull requests
- Code coverage is integrated (--coverage)
- Very clear error messages of where the test failures occur
- By default, will simulate a "good enough" browser environment called JSDOM
- Provides a fake browser-like environment if needed (window, document, DOM, etc) using jsdom
- Snapshots: Jest can create text-based snapshots of rendered components. These snapshots can be checked in and show API or large object changes alongside code changes in pull requests.
- Code coverage is integrated (`--coverage`)
- Very clear error messages showing where a test failure occurred
# How to use jest
## How to use Jest
- using `create-react-app` or other project generators, jest should already be preconfigured. Run `npm test` usually will trigger it!
- needs `jest.config.js`
- Using `create-react-app` or other project generators, Jest should already be pre-configured. Running `npm test` usually will trigger it!
- A `jest.config.js` file is used for configuration
- `jsdom` might not have enough API from real browsers, for those cases, polyfills are required. Place these inside `jest.setup.js` and hook up the setup file in `jest.config.js`
- in order to use `enzyme` library to test React Components, more config bits are needed inside `jest.setup.js`
# What does a test look like?
## What does a test look like?
```ts
// describe(), it() and expect() are globally exported, so they don't need to be imported when jest runs these tests
@@ -33,11 +32,15 @@ describe('Something to be tested', () => {
});
```
# Test React Components by using `enzyme`
## Testing React components using Enzyme
- use `enzyme` to `mount()` the component (as oppose to rendering)
- the `mount()` function will return a wrapper that can be inspected
- the wrapper has functionality like `find()`, simulating clicks, etc.
[Enzyme](https://airbnb.io/enzyme/) is made by Airbnb and provides utilities to help test React components.
In a real app using ReactDOM, the top-level component will be rendered on the page using `ReactDOM.render()`. Enzyme provides a lighter-weight `mount()` function which is usually adequate for testing purposes.
`mount()` returns a wrapper that can be inspected and provides functionality like `find()`, simulating clicks, etc.
The following code demonstrates how Enzyme can be used to help test React components.
```tsx
import React from 'react';
@@ -45,19 +48,32 @@ import { mount } from 'enzyme';
import { TestMe } from './TestMe';
describe('TestMe Component', () => {
it('should have a non-clickable component when the origina InnerMe is clicked', () => {
it('should have a non-clickable component when the original InnerMe is clicked', () => {
const wrapper = mount(<TestMe name="world" />);
wrapper.find('#innerMe').simulate('click');
expect(wrapper.find('#innerMe').text()).toBe('Clicked');
});
});
describe('Foo Component Tests', () => {
it('allows us to set props', () => {
const wrapper = mount(<Foo bar="baz" />);
expect(wrapper.props().bar).toBe('baz');
wrapper.setProps({ bar: 'foo' });
expect(wrapper.props().bar).toBe('foo');
wrapper.find('button').simulate('click');
});
});
```
# Advanced Topics
## Advanced topics
## Mocking
### Mocking
Mocking functions is a large part of what makes `jest` a powerful testing library. `jest` actually intercepts module inclusion process in `node.js` allowing it to mock entire modules if needed. There are many ways to mock as you can imagine in a language as flexible as JS. We only look at the simplest case but there's a lot of depth here.
Mocking functions is a large part of what makes Jest a powerful testing library. Jest actually intercepts the module loading process in Node.js, allowing it to mock entire modules if needed.
There are many ways to mock, as you'd imagine in a language as flexible as JS. We only look at the simplest case, but there's a lot of depth here.
To mock a function:
@@ -66,33 +82,30 @@ it('some test function', () => {
const mockCallback = jest.fn(x => 42 + x);
mockCallback(1);
mockCallback(2);
expect(mockCallback.mock.calls.length).toBe(2);
expect(mockCallback).toHaveBeenCalledTimes(2);
});
```
Read more about jest mocking here: https://jestjs.io/docs/en/mock-functions.html
Read more about jest mocking [here](https://jestjs.io/docs/en/mock-functions.html).
## Async Testing
### Async Testing
### callback
For testing async scenarios, the test runner needs some way to know when the scenario is finished. Jest tests can handle async scenarios using callbacks, promises, or async/await.
```ts
// Callback
it('tests callback functions', (done) => {
someFunctionThatCallsDone(done));
})
```
setTimeout(() => {
done();
}, 1000);
});
### promise
```ts
// Returning a promise
it('tests promise functions', () => {
return someFunctionThatReturnsPromise());
})
```
});
### (recommended) async / await
```ts
// Async/await (recommended)
it('tests async functions', async () => {
expect(await someFunction()).toBe(5);
});
@@ -100,50 +113,29 @@ it('tests async functions', async () => {
# Demo
## jest basics
## Jest basics
In this repo, we can start an inner loop development of tests with the command: `npm test`
Take a look at code inside `demo/src`:
1. `index.ts` is exports a few functions for a counter as well as a test for squaring numbers but demonstrates out jest uses mocks
1. `index.ts` exports a few functions for a counter as well as a function for squaring numbers. We'll use this last function to demonstrate how mocks work.
2. `multiply.ts` is a contrived example of a function that is exported
3. `index.spec.ts` is the test file: note how tests are re-run on save to test file changes as well as source code changes under `src`
3. `index.spec.ts` is the test file
## testing React applications
You can also test React Components with `jest` with the help of a partner library called `enzyme`. Take a look at the test below:
```ts
import { mount } from 'enzyme';
describe('Foo Component Tests', () => {
it('allows us to set props', () => {
const wrapper = mount(<Foo bar="baz" />);
expect(wrapper.props().bar).toBe('baz');
wrapper.setProps({ bar: 'foo' });
expect(wrapper.props().bar).toBe('foo');
});
});
```
`mount` does a full mount of the component. You can use the `enzyme` wrapper to simulate clicks, etc.:
```ts
wrapper.find('button').simulate('click');
```
Note how tests are re-run when either test files or source files under `src` are saved.
# Exercise
## Basic Testing
## Basic testing
1. Run the tests by running `npm test` at the root of the bootcamp project
2. Look at the `stack.ts` for a sample implementation of a stack
2. Look at `exercise/src/stack.ts` for a sample implementation of a stack
3. Follow the instructions inside the `stack.spec.ts` file to complete the two tests
3. Follow the instructions inside `stack.spec.ts` file to complete the two tests
## Enzyme Testing

View File

@@ -3,7 +3,7 @@ import { mount } from 'enzyme';
import { TestMe } from './TestMe';
describe('TestMe Component', () => {
it('should have a non-clickable component when the origina InnerMe is clicked', () => {
it('should have a non-clickable component when the original InnerMe is clicked', () => {
const wrapper = mount(<TestMe name="world" />);
wrapper.find('#innerMe').simulate('click');
expect(wrapper.find('#innerMe').text()).toBe('Clicked');

View File

@@ -1,11 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { TestMe } from './TestMe';
describe('TestMe Component', () => {
it('should have a non-clickable component when the origina InnerMe is clicked', () => {
const wrapper = mount(<TestMe name="world" />);
wrapper.find('#innerMe').simulate('click');
expect(wrapper.find('#innerMe').text()).toBe('Clicked');
describe('index', () => {
it('placeholder', () => {
});
});

View File

@@ -22,7 +22,7 @@ class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
return (
<Stack gap={10}>
<Stack horizontal horizontalAlign="center">
<Text variant="xxLarge">todos</Text>
<Text variant="xxLarge">todos - step2-07 demo</Text>
</Stack>
<Stack horizontal gap={10}>

View File

@@ -23,7 +23,7 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
return (
<Stack gap={10}>
<Stack horizontal horizontalAlign="center">
<Text variant="xxLarge">todos</Text>
<Text variant="xxLarge">todos - step2-07 exercise</Text>
</Stack>
<Stack horizontal gap={10}>

View File

@@ -24,7 +24,7 @@ class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
return (
<Stack gap={10}>
<Stack horizontal horizontalAlign="center">
<Text variant="xxLarge">todos</Text>
<Text variant="xxLarge">todos - step2-08 demo</Text>
</Stack>
<Stack horizontal gap={10}>

View File

@@ -24,7 +24,7 @@ class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
return (
<Stack gap={10}>
<Stack horizontal horizontalAlign="center">
<Text variant="xxLarge">todos</Text>
<Text variant="xxLarge">todos - step2-08 exercise</Text>
</Stack>
<Stack horizontal gap={10}>

View File

@@ -24,7 +24,7 @@ class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
return (
<Stack gap={10}>
<Stack horizontal horizontalAlign="center">
<Text variant="xxLarge">todos</Text>
<Text variant="xxLarge">todos - step2-09 demo</Text>
</Stack>
<Stack horizontal gap={10}>

View File

@@ -24,7 +24,7 @@ class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
return (
<Stack gap={10}>
<Stack horizontal horizontalAlign="center">
<Text variant="xxLarge">todos</Text>
<Text variant="xxLarge">todos - step2-09 exercise</Text>
</Stack>
<Stack horizontal gap={10}>