diff --git a/package.json b/package.json index 38f80794..aec5e8b3 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@angular/cli": "~8.0.0", "@angular/compiler-cli": "^8.0.0", "@angular/language-service": "^8.0.0", + "@testing-library/jest-dom": "^4.1.0", "@types/jest": "~24.0.11", "@types/node": "~12.0.3", "codelyzer": "^5.0.1", diff --git a/projects/testing-library/tests/counter/counter.spec.ts b/projects/testing-library/tests/counter/counter.spec.ts deleted file mode 100644 index 3efb256d..00000000 --- a/projects/testing-library/tests/counter/counter.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { render } from '../../src/public_api'; - -@Component({ - selector: 'counter', - template: ` - - Current Count: {{ counter }} - + - `, -}) -export class CounterComponent { - @Input() counter = 0; - - increment() { - this.counter += 1; - } - - decrement() { - this.counter -= 1; - } -} - -test('Counter actions via template syntax', async () => { - const { getByText, getByTestId, click } = await render('', { - declarations: [CounterComponent], - }); - - click(getByText('+')); - expect(getByText('Current Count: 11')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 11'); - - click(getByText('-')); - expect(getByText('Current Count: 10')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 10'); -}); - -test('Counter actions via template syntax with parameters', async () => { - const { getByText, getByTestId, click } = await render('', { - declarations: [CounterComponent], - componentProperties: { - counter: 10, - }, - }); - - click(getByText('+')); - expect(getByText('Current Count: 11')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 11'); - - click(getByText('-')); - expect(getByText('Current Count: 10')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 10'); -}); - -test('Counter actions via component syntax', async () => { - const { getByText, getByTestId, click } = await render(CounterComponent, { - declarations: [CounterComponent], - }); - - click(getByText('+')); - expect(getByText('Current Count: 1')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 1'); - - click(getByText('-')); - expect(getByText('Current Count: 0')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 0'); -}); - -test('Counter actions via component syntax without render options', async () => { - const { getByText, getByTestId, click } = await render(CounterComponent); - - click(getByText('+')); - expect(getByText('Current Count: 1')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 1'); - - click(getByText('-')); - expect(getByText('Current Count: 0')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 0'); -}); - -test('Counter actions via component syntax with parameters', async () => { - const { getByText, getByTestId, click } = await render(CounterComponent, { - declarations: [CounterComponent], - componentProperties: { - counter: 10, - }, - }); - - click(getByText('+')); - expect(getByText('Current Count: 11')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 11'); - - click(getByText('-')); - expect(getByText('Current Count: 10')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 10'); -}); - -test('Counter actions via component syntax without declaration', async () => { - const { getByText, getByTestId, click } = await render(CounterComponent, { - componentProperties: { - counter: 10, - }, - }); - - click(getByText('+')); - expect(getByText('Current Count: 11')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 11'); - - click(getByText('-')); - expect(getByText('Current Count: 10')).toBeTruthy(); - expect(getByTestId('count').textContent).toBe('Current Count: 10'); -}); diff --git a/projects/testing-library/tests/form/form.component.html b/projects/testing-library/tests/form/form.component.html deleted file mode 100644 index 31a1687e..00000000 --- a/projects/testing-library/tests/form/form.component.html +++ /dev/null @@ -1,16 +0,0 @@ - - - Username - - Password - - Submit - - - diff --git a/projects/testing-library/tests/form/form.component.ts b/projects/testing-library/tests/form/form.component.ts deleted file mode 100644 index 19445aad..00000000 --- a/projects/testing-library/tests/form/form.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component, Output, EventEmitter } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'login-form', - templateUrl: './form.component.html', -}) -export class LoginFormComponent { - @Output() handleLogin = new EventEmitter<{ username: string; password: string }>(); - - loginForm: FormGroup; - constructor(private fb: FormBuilder) { - this.createForm(); - } - - createForm() { - this.loginForm = this.fb.group({ - username: ['', Validators.required], - password: ['', Validators.required], - }); - } - - handleSubmit() { - if (this.loginForm.valid) { - this.handleLogin.emit(this.loginForm.value); - } - } -} diff --git a/projects/testing-library/tests/form/form.spec.ts b/projects/testing-library/tests/form/form.spec.ts deleted file mode 100644 index c70a4918..00000000 --- a/projects/testing-library/tests/form/form.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ReactiveFormsModule, FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { render } from '../../src/public_api'; -import { Component, Output, EventEmitter } from '@angular/core'; -import { LoginFormComponent } from './form.component'; - -test('login form submits using the component syntax', async () => { - const fakeUser = { username: 'jackiechan', password: 'hiya! 🥋' }; - const handleLogin = { - emit: jest.fn(), - }; - - const { container, getByLabelText, getByText, input, submit } = await render(LoginFormComponent, { - imports: [ReactiveFormsModule], - componentProperties: { - handleLogin: handleLogin as any, - }, - }); - - const usernameNode = getByLabelText(/username/i) as HTMLInputElement; - const passwordNode = getByLabelText(/password/i) as HTMLInputElement; - const submitButtonNode = getByText(/submit/i) as HTMLButtonElement; - const formNode = container.querySelector('form'); - - input(usernameNode, { - target: { - value: fakeUser.username, - }, - }); - - passwordNode.value = fakeUser.password; - input(passwordNode); - - submit(formNode); - - expect(handleLogin.emit).toHaveBeenCalledTimes(1); - expect(handleLogin.emit).toHaveBeenCalledWith(fakeUser); - expect(submitButtonNode.type).toBe('submit'); -}); diff --git a/projects/testing-library/tests/render.spec.ts b/projects/testing-library/tests/render.spec.ts index c3563b3b..cd83237e 100644 --- a/projects/testing-library/tests/render.spec.ts +++ b/projects/testing-library/tests/render.spec.ts @@ -5,35 +5,49 @@ import { render } from '../src/public_api'; @Component({ selector: 'fixture', - template: ``, + template: ` + + button + `, }) class FixtureComponent {} +test('creates queries and events', async () => { + const component = await render(FixtureComponent); + + component.input(component.getByTestId('input'), { target: { value: 'a super awesome input' } }); + component.getByDisplayValue('a super awesome input'); + component.click(component.getByText('button')); +}); + @NgModule({ declarations: [FixtureComponent], }) export class FixtureModule {} - -test('should not throw if component is declared in an import', async () => { - await render(FixtureComponent, { - imports: [FixtureModule], - excludeComponentDeclaration: true, +describe('excludeComponentDeclaration', () => { + test('should not throw if component is declared in an import', async () => { + await render(FixtureComponent, { + imports: [FixtureModule], + excludeComponentDeclaration: true, + }); }); }); -test('should add NoopAnimationsModule by default', async () => { - await render(FixtureComponent); - const noopAnimationsModule = TestBed.get(NoopAnimationsModule); - expect(noopAnimationsModule).toBeDefined(); -}); - -test('should not add NoopAnimationsModule if BrowserAnimationsModule is an import', async () => { - await render(FixtureComponent, { - imports: [BrowserAnimationsModule], +describe('animationModule', () => { + test('should add NoopAnimationsModule by default', async () => { + await render(FixtureComponent); + const noopAnimationsModule = TestBed.get(NoopAnimationsModule); + expect(noopAnimationsModule).toBeDefined(); }); - const browserAnimationsModule = TestBed.get(BrowserAnimationsModule); - expect(browserAnimationsModule).toBeDefined(); + test('should not add NoopAnimationsModule if BrowserAnimationsModule is an import', async () => { + await render(FixtureComponent, { + imports: [BrowserAnimationsModule], + }); - expect(() => TestBed.get(NoopAnimationsModule)).toThrow(); + const browserAnimationsModule = TestBed.get(BrowserAnimationsModule); + expect(browserAnimationsModule).toBeDefined(); + + expect(() => TestBed.get(NoopAnimationsModule)).toThrow(); + }); }); diff --git a/projects/testing-library/tests/wrapper.spec.ts b/projects/testing-library/tests/wrapper.spec.ts index 1fa3344f..63813764 100644 --- a/projects/testing-library/tests/wrapper.spec.ts +++ b/projects/testing-library/tests/wrapper.spec.ts @@ -20,7 +20,6 @@ class WrapperComponent implements OnInit { } test('allows for a custom wrapper', async () => { - jest.spyOn(console, 'log').mockImplementation(() => {}); const { getByText } = await render('', { declarations: [FixtureComponent], wrapper: WrapperComponent, diff --git a/src/app/__snapshots__/app.component.spec.ts.snap b/src/app/__snapshots__/app.component.spec.ts.snap deleted file mode 100644 index f395d977..00000000 --- a/src/app/__snapshots__/app.component.spec.ts.snap +++ /dev/null @@ -1,396 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`matches snapshot 1`] = ` - - - - - Welcome to app! - - - - - Here are some links to help you start: - - - - - - Tour of Heroes - - - - - - - CLI Documentation - - - - - - - Angular blog - - - - - - Greet - - - - Name: - - - - Age: - - - - Favorite color: - - - - - red - - - blue - - - yellow - - - pink - - - - mat select - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - Favorite animal: - - - - - - - - - - - - - - - - - - - - - - - - native html select - - - - - - - - - - - Saab - - - Mercedes - - - Audi - - - - - - - - - Select your car: - - - - - - - - - - - - - - - - - - - - - - - - - - Toggle Open/Close - - - - The box is now Open! - - - - - -`; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts new file mode 100644 index 00000000..0e7faa3f --- /dev/null +++ b/src/app/app-routing.module.ts @@ -0,0 +1,83 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SingleComponent } from './examples/00-single-component'; +import { NestedContainerComponent } from './examples/01-nested-component'; +import { InputOutputComponent } from './examples/02-input-output'; +import { FormsComponent } from './examples/03-forms'; +import { MaterialFormsComponent } from './examples/04-forms-with-material'; +import { ComponentWithProviderComponent } from './examples/05-component-provider'; +import { WithNgRxStoreComponent } from './examples/06-with-ngrx-store'; +import { WithNgRxMockStoreComponent } from './examples/07-with-ngrx-mock-store'; + +export const examples = [ + { + path: 'single-component', + component: SingleComponent, + data: { + name: 'Single component', + }, + }, + { + path: 'nested-component', + component: NestedContainerComponent, + data: { + name: 'Nested components', + }, + }, + { + path: 'input-output', + component: InputOutputComponent, + data: { + name: 'Input and Output', + }, + }, + { + path: 'forms', + component: FormsComponent, + data: { + name: 'Form', + }, + }, + { + path: 'forms-material', + component: MaterialFormsComponent, + data: { + name: 'Material form', + }, + }, + { + path: 'component-with-provider', + component: ComponentWithProviderComponent, + data: { + name: 'With provider', + }, + }, + { + path: 'with-ngrx-store', + component: WithNgRxStoreComponent, + data: { + name: 'With NgRx Store', + }, + }, + { + path: 'with-ngrx-mock-store', + component: WithNgRxMockStoreComponent, + data: { + name: 'With NgRx MockStore', + }, + }, +]; + +export const routes: Routes = [ + { + path: '', + children: examples, + }, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} diff --git a/src/app/app.component.css b/src/app/app.component.css index e69de29b..6d3bc67b 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -0,0 +1,17 @@ +.container { + display: flex; + flex-direction: column; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +.sidenav { + flex: 1; +} + +.sidenav-container { + padding: 10px; +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 18e269ce..04b5f476 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,69 +1,17 @@ - - - Welcome to {{ title }}! - - -Here are some links to help you start: - - - Tour of Heroes - - - CLI Documentation - - - Angular blog - - - -Greet - - - - Name: - - - - - Age: - - - - Favorite color: - - {{ color }} - - - mat select - - Favorite animal: - - -- - - {{ animal.name }} - - - + + + @testing-library/angular + - native html select - - Select your car: - - - Saab - Mercedes - Audi - - - + + + + {{ example.data.name }} + + - - Toggle Open/Close - - The box is now {{ isOpen ? 'Open' : 'Closed' }}! - + + + + diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts deleted file mode 100644 index d9a25a43..00000000 --- a/src/app/app.component.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { Store } from '@ngrx/store'; -import { provideMockStore } from '@ngrx/store/testing'; - -import { render } from '@testing-library/angular'; -import { provideMock } from '@testing-library/angular/jest-utils'; - -import { AppComponent } from './app.component'; -import { GreetService } from './greet.service'; -import { ReactiveFormsModule } from '@angular/forms'; -import { MaterialModule } from './material.mode'; - -test(`matches snapshot`, async () => { - const { container } = await render('', { - declarations: [AppComponent], - imports: [ReactiveFormsModule, MaterialModule], - providers: [provideMockStore()], - }); - expect(container).toMatchSnapshot(); -}); - -test(`should have a title`, async () => { - const { getByText } = await render('', { - declarations: [AppComponent], - imports: [ReactiveFormsModule, MaterialModule], - providers: [provideMockStore()], - }); - expect(getByText('Welcome to app!')).toBeDefined(); -}); - -test(`should render title in a h1 tag`, async () => { - const { container } = await render('', { - declarations: [AppComponent], - imports: [ReactiveFormsModule, MaterialModule], - providers: [provideMockStore()], - }); - expect(container.querySelector('h1').textContent).toContain('Welcome to app!'); -}); - -test(`should be able to get the Store`, async () => { - await render('', { - declarations: [AppComponent], - imports: [ReactiveFormsModule, MaterialModule], - providers: [provideMockStore()], - }); - expect(TestBed.get>(Store)).toBeDefined(); -}); - -test(`should provide a mock greet service`, async () => { - const component = await render(AppComponent, { - declarations: [AppComponent], - imports: [ReactiveFormsModule, MaterialModule], - providers: [provideMockStore(), provideMock(GreetService)], - }); - const service: GreetService = TestBed.get(GreetService); - - component.click(component.getByText('Greet')); - - expect(service.greet).toHaveBeenCalled(); -}); - -describe('Forms', () => { - test(`should have form validations`, async () => { - const component = await render(AppComponent, { - imports: [ReactiveFormsModule, MaterialModule], - providers: [provideMockStore()], - }); - - const appComponent = component.fixture.componentInstance as AppComponent; - expect(appComponent.form.valid).toBe(false); - - const nameInput = component.getByLabelText('Name:'); - const ageInput = component.getByLabelText('Age:'); - const colorInput = component.getByLabelText('Favorite color:'); - const animalInput = component.getByLabelText('Favorite animal:'); - const carInput = component.getByLabelText(/car/); - - const nameValue = appComponent.form.get('name'); - const ageValue = appComponent.form.get('age'); - - component.type(nameInput, 'B'); - expect(nameValue.valid).toBe(false); - - component.type(nameInput, 'Bob'); - expect(nameValue.valid).toBe(true); - - component.type(ageInput, '17'); - expect(ageValue.valid).toBe(false); - - component.type(ageInput, '61'); - expect(ageValue.valid).toBe(false); - - component.type(ageInput, '20'); - expect(ageValue.valid).toBe(true); - - component.selectOptions(colorInput, 'ink', { exact: false }); - component.selectOptions(colorInput, /YELLOW/i); - component.selectOptions(animalInput, 'Cow'); - component.selectOptions(carInput, 'Audi'); - - expect(appComponent.form.valid).toBe(true); - expect(appComponent.form.value).toEqual({ - name: 'Bob', - age: 20, - color: 'yellow', - animal: { name: 'Cow', sound: 'Moo!' }, - car: 'audi', - }); - }); -}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 878211cf..5b20ef63 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,67 +1,11 @@ import { Component } from '@angular/core'; -import { trigger, state, style, transition, animate } from '@angular/animations'; -import { Store } from '@ngrx/store'; -import { GreetService } from './greet.service'; -import { FormBuilder, Validators } from '@angular/forms'; +import { examples as routes } from './app-routing.module'; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.css'], - animations: [ - trigger('openClose', [ - state( - 'open', - style({ - height: '200px', - opacity: 1, - backgroundColor: 'yellow', - }), - ), - state( - 'closed', - style({ - height: '100px', - opacity: 0.5, - backgroundColor: 'green', - }), - ), - transition('open => closed', [animate('1s')]), - transition('closed => open', [animate('0.5s')]), - ]), - ], + styleUrls: ['app.component.css'], }) export class AppComponent { - isOpen = true; - title = 'app'; - - colors = ['red', 'blue', 'yellow', 'pink']; - animals = [ - { name: 'Dog', sound: 'Woof!' }, - { name: 'Cat', sound: 'Meow!' }, - { name: 'Cow', sound: 'Moo!' }, - { name: 'Fox', sound: 'Wa-pa-pa-pa-pa-pa-pow!' }, - ]; - - form = this.fb.group({ - name: ['', [Validators.required, Validators.minLength(2)]], - age: ['', [Validators.min(18), Validators.max(28)]], - color: [], - animal: [], - car: [], - }); - - constructor(private store: Store, private greetService: GreetService, private fb: FormBuilder) {} - - greet() { - this.greetService.greet(); - } - - onSubmit() { - console.log('Form submitted: ', this.form.value); - } - - toggle() { - this.isOpen = !this.isOpen; - } + examples = routes; } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1cbdb449..6cdb32fc 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,13 +4,54 @@ import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { StoreModule } from '@ngrx/store'; +import { AppRoutingModule } from './app-routing.module'; +import { MaterialModule } from './material.module'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatToolbarModule } from '@angular/material/toolbar'; + import { AppComponent } from './app.component'; -import { MaterialModule } from './material.mode'; +import { SingleComponent } from './examples/00-single-component'; +import { NestedButtonComponent, NestedValueComponent, NestedContainerComponent } from './examples/01-nested-component'; +import { InputOutputComponent } from './examples/02-input-output'; +import { FormsComponent } from './examples/03-forms'; +import { MaterialFormsComponent } from './examples/04-forms-with-material'; +import { ComponentWithProviderComponent } from './examples/05-component-provider'; +import { WithNgRxStoreComponent, reducer } from './examples/06-with-ngrx-store'; +import { WithNgRxMockStoreComponent } from './examples/07-with-ngrx-mock-store'; @NgModule({ - declarations: [AppComponent], - imports: [BrowserModule, ReactiveFormsModule, BrowserAnimationsModule, StoreModule.forRoot({}), MaterialModule], - providers: [], + declarations: [ + AppComponent, + SingleComponent, + NestedButtonComponent, + NestedValueComponent, + NestedContainerComponent, + InputOutputComponent, + FormsComponent, + MaterialFormsComponent, + ComponentWithProviderComponent, + WithNgRxStoreComponent, + WithNgRxMockStoreComponent, + ], + imports: [ + BrowserModule, + ReactiveFormsModule, + BrowserAnimationsModule, + MaterialModule, + MatIconModule, + MatListModule, + MatSidenavModule, + MatToolbarModule, + AppRoutingModule, + StoreModule.forRoot({ + value: reducer, + items: function() { + return ['One', 'Two', 'Three']; + }, + }), + ], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/src/app/examples/00-single-component.spec.ts b/src/app/examples/00-single-component.spec.ts new file mode 100644 index 00000000..1708f9ad --- /dev/null +++ b/src/app/examples/00-single-component.spec.ts @@ -0,0 +1,20 @@ +import { render } from '@testing-library/angular'; + +import { SingleComponent } from './00-single-component'; + +test('renders the current value and can increment and decrement', async () => { + const component = await render(SingleComponent); + + const incrementControl = component.getByText('Increment'); + const decrementControl = component.getByText('Decrement'); + const valueControl = component.getByTestId('value'); + + expect(valueControl.textContent).toBe('0'); + + component.click(incrementControl); + component.click(incrementControl); + expect(valueControl.textContent).toBe('2'); + + component.click(decrementControl); + expect(valueControl.textContent).toBe('1'); +}); diff --git a/src/app/examples/00-single-component.ts b/src/app/examples/00-single-component.ts new file mode 100644 index 00000000..25001036 --- /dev/null +++ b/src/app/examples/00-single-component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-fixture', + template: ` + Decrement + {{ value }} + Increment + `, +}) +export class SingleComponent { + value = 0; +} diff --git a/src/app/examples/01-nested-component.spec.ts b/src/app/examples/01-nested-component.spec.ts new file mode 100644 index 00000000..758f51eb --- /dev/null +++ b/src/app/examples/01-nested-component.spec.ts @@ -0,0 +1,22 @@ +import { render } from '@testing-library/angular'; + +import { NestedButtonComponent, NestedValueComponent, NestedContainerComponent } from './01-nested-component'; + +test('renders the current value and can increment and decrement', async () => { + const component = await render(NestedContainerComponent, { + declarations: [NestedButtonComponent, NestedValueComponent], + }); + + const incrementControl = component.getByText('Increment'); + const decrementControl = component.getByText('Decrement'); + const valueControl = component.getByTestId('value'); + + expect(valueControl.textContent).toBe('0'); + + component.click(incrementControl); + component.click(incrementControl); + expect(valueControl.textContent).toBe('2'); + + component.click(decrementControl); + expect(valueControl.textContent).toBe('1'); +}); diff --git a/src/app/examples/01-nested-component.ts b/src/app/examples/01-nested-component.ts new file mode 100644 index 00000000..1fa87307 --- /dev/null +++ b/src/app/examples/01-nested-component.ts @@ -0,0 +1,34 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-button', + template: ` + {{ name }} + `, +}) +export class NestedButtonComponent { + @Input() name: string; + @Output() raise = new EventEmitter(); +} + +@Component({ + selector: 'app-value', + template: ` + {{ value }} + `, +}) +export class NestedValueComponent { + @Input() value: number; +} + +@Component({ + selector: 'app-fixture', + template: ` + + + + `, +}) +export class NestedContainerComponent { + value = 0; +} diff --git a/src/app/examples/02-input-output.spec.ts b/src/app/examples/02-input-output.spec.ts new file mode 100644 index 00000000..69e53c99 --- /dev/null +++ b/src/app/examples/02-input-output.spec.ts @@ -0,0 +1,31 @@ +import { render } from '@testing-library/angular'; + +import { InputOutputComponent } from './02-input-output'; + +test('is possible to set input and listen for output', async () => { + const sendValue = jest.fn(); + + const component = await render(InputOutputComponent, { + componentProperties: { + value: 47, + sendValue: { + emit: sendValue, + } as any, + }, + }); + + const incrementControl = component.getByText('Increment'); + const valueControl = component.getByTestId('value'); + const sendControl = component.getByText('Send'); + + expect(valueControl.textContent).toBe('47'); + + component.click(incrementControl); + component.click(incrementControl); + component.click(incrementControl); + expect(valueControl.textContent).toBe('50'); + + component.click(sendControl); + expect(sendValue).toHaveBeenCalledTimes(1); + expect(sendValue).toHaveBeenCalledWith(50); +}); diff --git a/src/app/examples/02-input-output.ts b/src/app/examples/02-input-output.ts new file mode 100644 index 00000000..a7ef9ce4 --- /dev/null +++ b/src/app/examples/02-input-output.ts @@ -0,0 +1,16 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-fixture', + template: ` + Decrement + {{ value }} + Increment + + Send + `, +}) +export class InputOutputComponent { + @Input() value = 0; + @Output() sendValue = new EventEmitter(); +} diff --git a/src/app/examples/03-forms.spec.ts b/src/app/examples/03-forms.spec.ts new file mode 100644 index 00000000..d7f6758c --- /dev/null +++ b/src/app/examples/03-forms.spec.ts @@ -0,0 +1,45 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { render } from '@testing-library/angular'; + +import { FormsComponent } from './03-forms'; + +test('is possible to fill in a form and verify error messages (with the help of jest-dom https://testing-library.com/docs/ecosystem-jest-dom)', async () => { + const component = await render(FormsComponent, { + imports: [ReactiveFormsModule], + }); + + const nameControl = component.getByLabelText('Name'); + const scoreControl = component.getByLabelText(/score/i); + const colorControl = component.getByLabelText('color', { exact: false }); + const errors = component.getByRole('alert'); + + expect(errors).toContainElement(component.queryByText('name is required')); + expect(errors).toContainElement(component.queryByText('score must be greater than 1')); + expect(errors).toContainElement(component.queryByText('color is required')); + + expect(nameControl).toBeInvalid(); + component.type(nameControl, 'Tim'); + component.type(scoreControl, '12'); + component.selectOptions(colorControl, 'Green'); + + expect(component.queryByText('name is required')).not.toBeInTheDocument(); + expect(component.queryByText('score must be lesser than 10')).toBeInTheDocument(); + expect(component.queryByText('color is required')).not.toBeInTheDocument(); + + expect(scoreControl).toBeInvalid(); + component.type(scoreControl, '7'); + expect(scoreControl).toBeValid(); + + expect(errors).not.toBeInTheDocument(); + + expect(nameControl).toHaveValue('Tim'); + expect(scoreControl).toHaveValue(7); + expect(colorControl).toHaveValue('G'); + + const form = component.getByTestId('my-form'); + expect(form).toHaveFormValues({ + name: 'Tim', + score: 7, + color: 'G', + }); +}); diff --git a/src/app/examples/03-forms.ts b/src/app/examples/03-forms.ts new file mode 100644 index 00000000..c1649150 --- /dev/null +++ b/src/app/examples/03-forms.ts @@ -0,0 +1,63 @@ +import { Component } from '@angular/core'; +import { FormBuilder, Validators, ReactiveFormsModule, ValidationErrors } from '@angular/forms'; + +@Component({ + selector: 'app-fixture', + template: ` + + + Name + + + + + Score + + + + + Color + + --- + {{ color.value }} + + + + + {{ error }} + + + `, +}) +export class FormsComponent { + colors = [{ id: 'R', value: 'Red' }, { id: 'B', value: 'Blue' }, { id: 'G', value: 'Green' }]; + form = this.formBuilder.group({ + name: ['', Validators.required], + score: [0, [Validators.min(1), Validators.max(10)]], + color: ['', Validators.required], + }); + + constructor(private formBuilder: FormBuilder) {} + + get formErrors() { + return Object.keys(this.form.controls) + .map(formKey => { + const controlErrors: ValidationErrors = this.form.get(formKey).errors; + if (controlErrors != null) { + return Object.keys(controlErrors).map(keyError => { + const error = controlErrors[keyError]; + switch (keyError) { + case 'required': + return `${formKey} is required`; + case 'min': + return `${formKey} must be greater than ${error.min}`; + case 'max': + return `${formKey} must be lesser than ${error.max}`; + } + }); + } + }) + .reduce((errors, value) => errors.concat(value), []) + .filter(Boolean); + } +} diff --git a/src/app/examples/04-forms-with-material.spec.ts b/src/app/examples/04-forms-with-material.spec.ts new file mode 100644 index 00000000..faca26b2 --- /dev/null +++ b/src/app/examples/04-forms-with-material.spec.ts @@ -0,0 +1,46 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { render } from '@testing-library/angular'; + +import { MaterialModule } from '../material.module'; +import { MaterialFormsComponent } from './04-forms-with-material'; + +test('is possible to fill in a form and verify error messages (with the help of jest-dom https://testing-library.com/docs/ecosystem-jest-dom)', async () => { + const component = await render(MaterialFormsComponent, { + imports: [ReactiveFormsModule, MaterialModule], + }); + + const nameControl = component.getByPlaceholderText('Name'); + const scoreControl = component.getByPlaceholderText(/score/i); + const colorControl = component.getByPlaceholderText('color', { exact: false }); + const errors = component.getByRole('alert'); + + expect(errors).toContainElement(component.queryByText('name is required')); + expect(errors).toContainElement(component.queryByText('score must be greater than 1')); + expect(errors).toContainElement(component.queryByText('color is required')); + + component.type(nameControl, 'Tim'); + component.type(scoreControl, '12'); + component.selectOptions(colorControl, 'Green'); + + expect(component.queryByText('name is required')).not.toBeInTheDocument(); + expect(component.queryByText('score must be lesser than 10')).toBeInTheDocument(); + expect(component.queryByText('color is required')).not.toBeInTheDocument(); + + expect(scoreControl).toBeInvalid(); + component.type(scoreControl, '7'); + expect(scoreControl).toBeValid(); + + expect(errors).not.toBeInTheDocument(); + + expect(nameControl).toHaveValue('Tim'); + expect(scoreControl).toHaveValue(7); + + const form = component.getByTestId('my-form'); + expect(form).toHaveFormValues({ + name: 'Tim', + score: 7, + }); + + // not added to the form? + expect((component.fixture.componentInstance as MaterialFormsComponent).form.get('color').value).toBe('G'); +}); diff --git a/src/app/examples/04-forms-with-material.ts b/src/app/examples/04-forms-with-material.ts new file mode 100644 index 00000000..554d3065 --- /dev/null +++ b/src/app/examples/04-forms-with-material.ts @@ -0,0 +1,69 @@ +import { Component } from '@angular/core'; +import { FormBuilder, Validators, ReactiveFormsModule, ValidationErrors } from '@angular/forms'; + +@Component({ + selector: 'app-fixture', + template: ` + + + + + + + + + + + + --- + {{ color.value }} + + + + + {{ error }} + + + `, +}) +export class MaterialFormsComponent { + colors = [{ id: 'R', value: 'Red' }, { id: 'B', value: 'Blue' }, { id: 'G', value: 'Green' }]; + form = this.formBuilder.group({ + name: ['', Validators.required], + score: [0, [Validators.min(1), Validators.max(10)]], + color: ['', Validators.required], + }); + + constructor(private formBuilder: FormBuilder) {} + + get formErrors() { + return Object.keys(this.form.controls) + .map(formKey => { + const controlErrors: ValidationErrors = this.form.get(formKey).errors; + if (controlErrors != null) { + return Object.keys(controlErrors).map(keyError => { + const error = controlErrors[keyError]; + switch (keyError) { + case 'required': + return `${formKey} is required`; + case 'min': + return `${formKey} must be greater than ${error.min}`; + case 'max': + return `${formKey} must be lesser than ${error.max}`; + } + }); + } + }) + .reduce((errors, value) => errors.concat(value), []) + .filter(Boolean); + } +} diff --git a/src/app/examples/05-component-provider.spec.ts b/src/app/examples/05-component-provider.spec.ts new file mode 100644 index 00000000..0ab5fc28 --- /dev/null +++ b/src/app/examples/05-component-provider.spec.ts @@ -0,0 +1,52 @@ +import { render } from '@testing-library/angular'; + +import { ComponentWithProviderComponent, CounterService } from './05-component-provider'; + +test('renders the current value and can increment and decrement', async () => { + const component = await render(ComponentWithProviderComponent, { + componentProviders: [ + { + provide: CounterService, + useValue: new CounterService(), + }, + ], + }); + + const incrementControl = component.getByText('Increment'); + const decrementControl = component.getByText('Decrement'); + const valueControl = component.getByTestId('value'); + + expect(valueControl.textContent).toBe('0'); + + component.click(incrementControl); + component.click(incrementControl); + expect(valueControl.textContent).toBe('2'); + + component.click(decrementControl); + expect(valueControl.textContent).toBe('1'); +}); + +// test('renders the current value and can increment and decrement with a mocked jest-utils service', async () => { +// const component = await render(FixtureComponent, { +// componentProviders: [provideMock(CounterService)], +// }); + +// const counter = TestBed.get(CounterService) as Mock; +// let fakeCounter = 50; +// counter.increment.mockImplementation(() => (fakeCounter += 10)); +// counter.decrement.mockImplementation(() => (fakeCounter -= 10)); +// counter.value.mockImplementation(() => fakeCounter); + +// const incrementControl = component.getByText('Increment'); +// const decrementControl = component.getByText('Decrement'); +// const valueControl = component.getByTestId('value'); + +// expect(valueControl.textContent).toBe('50'); + +// component.click(incrementControl); +// component.click(incrementControl); +// expect(valueControl.textContent).toBe('70'); + +// component.click(decrementControl); +// expect(valueControl.textContent).toBe('60'); +// }); diff --git a/src/app/examples/05-component-provider.ts b/src/app/examples/05-component-provider.ts new file mode 100644 index 00000000..4d933dbb --- /dev/null +++ b/src/app/examples/05-component-provider.ts @@ -0,0 +1,33 @@ +import { Component, Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class CounterService { + private _value = 0; + + increment() { + this._value += 1; + } + + decrement() { + this._value -= 1; + } + + value() { + return this._value; + } +} + +@Component({ + selector: 'app-fixture', + template: ` + Decrement + {{ counter.value() }} + Increment + `, + providers: [CounterService], +}) +export class ComponentWithProviderComponent { + constructor(public counter: CounterService) {} +} diff --git a/src/app/examples/06-with-ngrx-store.spec.ts b/src/app/examples/06-with-ngrx-store.spec.ts new file mode 100644 index 00000000..b20cbc68 --- /dev/null +++ b/src/app/examples/06-with-ngrx-store.spec.ts @@ -0,0 +1,32 @@ +import { render } from '@testing-library/angular'; +import { StoreModule } from '@ngrx/store'; + +import { WithNgRxStoreComponent, reducer } from './06-with-ngrx-store'; + +test('works with ngrx store', async () => { + const component = await render(WithNgRxStoreComponent, { + imports: [ + StoreModule.forRoot( + { + value: reducer, + }, + { + runtimeChecks: {}, + }, + ), + ], + }); + + const incrementControl = component.getByText('Increment'); + const decrementControl = component.getByText('Decrement'); + const valueControl = component.getByTestId('value'); + + expect(valueControl.textContent).toBe('0'); + + component.click(incrementControl); + component.click(incrementControl); + expect(valueControl.textContent).toBe('20'); + + component.click(decrementControl); + expect(valueControl.textContent).toBe('10'); +}); diff --git a/src/app/examples/06-with-ngrx-store.ts b/src/app/examples/06-with-ngrx-store.ts new file mode 100644 index 00000000..4c725b1d --- /dev/null +++ b/src/app/examples/06-with-ngrx-store.ts @@ -0,0 +1,36 @@ +import { Component } from '@angular/core'; +import { createSelector, Store, createAction, createReducer, on, select } from '@ngrx/store'; + +const increment = createAction('increment'); +const decrement = createAction('decrement'); +const counterReducer = createReducer(0, on(increment, state => state + 1), on(decrement, state => state - 1)); + +export function reducer(state, action) { + return counterReducer(state, action); +} + +const selectValue = createSelector( + (state: any) => state.value, + value => value * 10, +); + +@Component({ + selector: 'app-fixture', + template: ` + Decrement + {{ value | async }} + Increment + `, +}) +export class WithNgRxStoreComponent { + value = this.store.pipe(select(selectValue)); + constructor(private store: Store) {} + + increment() { + this.store.dispatch(increment()); + } + + decrement() { + this.store.dispatch(decrement()); + } +} diff --git a/src/app/examples/07-with-ngrx-mock-store.spec.ts b/src/app/examples/07-with-ngrx-mock-store.spec.ts new file mode 100644 index 00000000..9eab2519 --- /dev/null +++ b/src/app/examples/07-with-ngrx-mock-store.spec.ts @@ -0,0 +1,22 @@ +import { render } from '@testing-library/angular'; +import { provideMockStore } from '@ngrx/store/testing'; + +import { WithNgRxMockStoreComponent, selectItems } from './07-with-ngrx-mock-store'; + +test('works with provideMockStore', async () => { + const component = await render(WithNgRxMockStoreComponent, { + providers: [ + provideMockStore({ + selectors: [ + { + selector: selectItems, + value: ['Four', 'Seven'], + }, + ], + }), + ], + }); + + component.getByText('Four'); + component.getByText('Seven'); +}); diff --git a/src/app/examples/07-with-ngrx-mock-store.ts b/src/app/examples/07-with-ngrx-mock-store.ts new file mode 100644 index 00000000..dfe93162 --- /dev/null +++ b/src/app/examples/07-with-ngrx-mock-store.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; +import { createSelector, Store, select } from '@ngrx/store'; + +export const selectItems = createSelector( + (state: any) => state.items, + items => items, +); + +@Component({ + selector: 'app-fixture', + template: ` + + {{ item }} + + `, +}) +export class WithNgRxMockStoreComponent { + items = this.store.pipe(select(selectItems)); + constructor(private store: Store) {} +} diff --git a/src/app/greet.service.ts b/src/app/greet.service.ts deleted file mode 100644 index 93991444..00000000 --- a/src/app/greet.service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root', -}) -export class GreetService { - greet(): void { - console.log('👋👋'); - } -} diff --git a/src/app/material.mode.ts b/src/app/material.module.ts similarity index 66% rename from src/app/material.mode.ts rename to src/app/material.module.ts index 9f5c3368..fb7970b9 100644 --- a/src/app/material.mode.ts +++ b/src/app/material.module.ts @@ -1,9 +1,9 @@ import { NgModule } from '@angular/core'; -import { A11yModule } from '@angular/cdk/a11y'; + import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; @NgModule({ - exports: [A11yModule, MatInputModule, MatSelectModule], + exports: [MatInputModule, MatSelectModule], }) export class MaterialModule {} diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json index 8ea061ea..322e6d99 100644 --- a/src/tsconfig.app.json +++ b/src/tsconfig.app.json @@ -4,8 +4,5 @@ "outDir": "../out-tsc/app", "types": [] }, - "exclude": [ - "src/test.ts", - "**/*.spec.ts" - ] + "exclude": ["**/test.ts", "**/*.spec.ts"] } diff --git a/test.ts b/test.ts index 8d88704e..8ef67d09 100644 --- a/test.ts +++ b/test.ts @@ -1 +1,2 @@ import 'jest-preset-angular'; +import '@testing-library/jest-dom/extend-expect'; diff --git a/yarn.lock b/yarn.lock index 10e2427b..ffd7596d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -332,6 +332,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.5.1": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" + integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" @@ -519,6 +526,15 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^12.0.9" +"@jest/types@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" + integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^13.0.0" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -738,6 +754,21 @@ pretty-format "^24.8.0" wait-for-expect "^1.2.0" +"@testing-library/jest-dom@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.1.0.tgz#69d372e54e4e33be3fd34f3848ec0e8e9d099276" + integrity sha512-cKAONDmJKGJ2DSu6R/+lgA8i8uyZIx4CaOiiK0yMjp+2UecH6kfjunJiy5hfExKMtR74eyzFriqO1w9aTC8VyQ== + dependencies: + "@babel/runtime" "^7.5.1" + chalk "^2.4.1" + css "^2.2.3" + css.escape "^1.5.1" + jest-diff "^24.0.0" + jest-matcher-utils "^24.0.0" + lodash "^4.17.11" + pretty-format "^24.0.0" + redent "^3.0.0" + "@types/babel__core@^7.1.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f" @@ -863,11 +894,23 @@ "@types/source-list-map" "*" source-map "^0.6.1" +"@types/yargs-parser@*": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0" + integrity sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw== + "@types/yargs@^12.0.2", "@types/yargs@^12.0.9": version "12.0.12" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw== +"@types/yargs@^13.0.0": + version "13.0.2" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.2.tgz#a64674fc0149574ecd90ba746e932b5a5f7b3653" + integrity sha512-lwwgizwk/bIIU+3ELORkyuOgDjCh7zuWDFqRtPPhhVgq9N1F7CvLNKg1TX4f2duwtKQ0p044Au9r1PLIXHrIzQ== + dependencies: + "@types/yargs-parser" "*" + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -2739,6 +2782,21 @@ css-selector-tokenizer@^0.7.1: fastparse "^1.1.1" regexpu-core "^1.0.0" +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + +css@^2.2.3: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + cssauron@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/cssauron/-/cssauron-1.4.0.tgz#a6602dff7e04a8306dc0db9a551e92e8b5662ad8" @@ -3051,6 +3109,11 @@ diff-sequences@^24.3.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975" integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw== +diff-sequences@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" + integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== + diff@^3.2.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -4480,6 +4543,11 @@ indent-string@^3.0.0, indent-string@^3.2.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" @@ -5119,6 +5187,16 @@ jest-config@^24.8.0: pretty-format "^24.8.0" realpath-native "^1.1.0" +jest-diff@^24.0.0, jest-diff@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" + integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== + dependencies: + chalk "^2.0.1" + diff-sequences "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + jest-diff@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172" @@ -5184,6 +5262,11 @@ jest-get-type@^24.8.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc" integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ== +jest-get-type@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" + integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== + jest-haste-map@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.0.tgz#51794182d877b3ddfd6e6d23920e3fe72f305800" @@ -5232,6 +5315,16 @@ jest-leak-detector@^24.8.0: dependencies: pretty-format "^24.8.0" +jest-matcher-utils@^24.0.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" + integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== + dependencies: + chalk "^2.0.1" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + jest-matcher-utils@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495" @@ -6441,6 +6534,11 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +min-indent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256" + integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY= + mini-css-extract-plugin@0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.6.0.tgz#a3f13372d6fcde912f3ee4cd039665704801e3b9" @@ -7788,6 +7886,16 @@ pretty-format@^24.0.0, pretty-format@^24.8.0: ansi-styles "^3.2.0" react-is "^16.8.4" +pretty-format@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" + integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== + dependencies: + "@jest/types" "^24.9.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" + process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" @@ -8229,6 +8337,14 @@ redent@^2.0.0: indent-string "^3.0.0" strip-indent "^2.0.0" +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + redeyed@~2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" @@ -9056,7 +9172,7 @@ source-map-loader@0.2.4: async "^2.5.0" loader-utils "^1.1.0" -source-map-resolve@^0.5.0: +source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== @@ -9446,6 +9562,13 @@ strip-indent@^2.0.0: resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
- The box is now Open! -
The box is now {{ isOpen ? 'Open' : 'Closed' }}!
{{ error }}