Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filtering #47

Merged
merged 6 commits into from
Nov 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ type SkillGroupData = {
skillCount: SkillCount;
selectedSkillCount: SkillCount;
resetSkills: () => void;
handleFilter: (query: string) => void;
};

type SkillCount = {
Expand All @@ -180,6 +181,18 @@ type SavedDataType = {

## Features

### Filtering

The `<SkillTreeGroup />` component exposes the `handleFilter()` method which can be used to close any trees that don't contain skills that match the query. This can be used in conjunction with your own input component like so:

```tsx
<input
style={{ height: '32px' }}
onChange={e => handleFilter(e.target.value)}
placeholder="Filter through trees..."
/>
```

### Custom Themes

It's likely that you're application won't look to hot with a dark blue/rainbow themed skill tree. Fortunately, a custom theme can be supplied to the `SkillTreeGroup` component. The styles passed through will override the defaults to allow your skill tree to fit nicely into your application. The theme object's type is exported in the package as `SkillThemeType`. I don't perform any object merging between the default styles and the user-defined object, so you'll need to fill out the whole object.
Expand Down
18 changes: 18 additions & 0 deletions example/components/FIlterInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';

interface Props {
handleFilter: (query: string) => void;
}

function FilterInput(props: Props) {
const { handleFilter } = props;
return (
<input
style={{ height: '32px' }}
onChange={e => handleFilter(e.target.value)}
placeholder="Filter through trees..."
/>
);
}

export default FilterInput;
3 changes: 3 additions & 0 deletions example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import './index.css';
import { legsPushData, legsPullData, hpSavedData } from './mockData';
import { ContextStorage } from '../src/models';
import FilterInput from './components/FIlterInput';

function handleSave(
storage: ContextStorage,
Expand All @@ -35,6 +36,7 @@ const App = () => {
skillCount,
selectedSkillCount,
resetSkills,
handleFilter,
}: SkillGroupDataType) => {
const totalSkillCount = skillCount.optional + skillCount.required;
const totalSelectedCount =
Expand All @@ -60,6 +62,7 @@ const App = () => {
Reset
</button>
</div>
<FilterInput handleFilter={handleFilter} />
<SkillTree
treeId="sp"
title="Squat Progression"
Expand Down
164 changes: 164 additions & 0 deletions src/__tests__/integration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ function renderComponent(
skillCount,
selectedSkillCount,
resetSkills,
handleFilter,
}: SkillGroupDataType) => {
const totalSkillCount = skillCount.required + skillCount.optional;
const totalSelectedSkillCount =
Expand All @@ -167,6 +168,10 @@ function renderComponent(
<button data-testid="reset-button" onClick={resetSkills}>
Reset
</button>
<input
placeholder="filter"
onChange={({ target }) => handleFilter(target.value)}
/>
</h2>
<SkillTree treeId="fe" title="Frontend" data={simpleData} />
<SkillTree
Expand Down Expand Up @@ -528,4 +533,163 @@ describe('SkillTreeGroup component', () => {
expect(htmlNode).toHaveStyleRule('background', 'grey');
});
});

describe('filtering', () => {
it('should display the tree when no query has been made', () => {
const { getByPlaceholderText, getAllByTestId } = renderComponent([]);

const filterInput = getByPlaceholderText('filter');
const [visibilityContainer] = getAllByTestId('visibility-container');

expect(visibilityContainer).toHaveStyle('opacity: 1');

fireEvent.change(filterInput, {
target: {
value: 'ppppa',
},
});

expect(visibilityContainer).toHaveStyle('opacity: 0');
});

it('should display the tree if the query contains a matching skillid', () => {
const { getByPlaceholderText, getAllByTestId } = renderComponent([]);

const filterInput = getByPlaceholderText('filter');
const [visibilityContainer] = getAllByTestId('visibility-container');

expect(visibilityContainer).toHaveStyle('opacity: 1');

fireEvent.change(filterInput, {
target: {
value: 'java',
},
});

expect(visibilityContainer).toHaveStyle('opacity: 1');
});

it('should display the tree if no valid query is present', () => {
const { getByPlaceholderText, getAllByTestId } = renderComponent([]);

const filterInput = getByPlaceholderText('filter');
const [visibilityContainer] = getAllByTestId('visibility-container');

expect(visibilityContainer).toHaveStyle('opacity: 1');

fireEvent.change(filterInput, {
target: {
value: null,
},
});

expect(visibilityContainer).toHaveStyle('opacity: 1');
});

it('should not display the tree if the query does not contain a matching skillid', () => {
const { getByPlaceholderText, getAllByTestId } = renderComponent([]);

const filterInput = getByPlaceholderText('filter');
const [visibilityContainer] = getAllByTestId('visibility-container');

expect(visibilityContainer).toHaveStyle('opacity: 1');

fireEvent.change(filterInput, {
target: {
value: 'javascrooo',
},
});

expect(visibilityContainer).toHaveStyle('opacity: 0');
});

it('should cause a tree to hide/show when the filter query changes', () => {
const { getByPlaceholderText, getAllByTestId } = renderComponent([]);

const filterInput = getByPlaceholderText('filter');
const [visibilityContainer] = getAllByTestId('visibility-container');

expect(visibilityContainer).toHaveStyle('opacity: 1');

fireEvent.change(filterInput, {
target: {
value: 'javascrooo',
},
});

expect(visibilityContainer).toHaveStyle('opacity: 0');

fireEvent.change(filterInput, {
target: {
value: 'javascroo',
},
});

expect(visibilityContainer).toHaveStyle('opacity: 0');

fireEvent.change(filterInput, {
target: {
value: 'java',
},
});

expect(visibilityContainer).toHaveStyle('opacity: 1');

fireEvent.change(filterInput, {
target: {
value: 'javajava',
},
});

expect(visibilityContainer).toHaveStyle('opacity: 0');

fireEvent.change(filterInput, {
target: {
value: null,
},
});

expect(visibilityContainer).toHaveStyle('opacity: 1');
});

it('should only display the trees that contain skillIds that match the filter query', () => {
const { getByPlaceholderText, getAllByTestId } = renderComponent(
complexData
);

const filterInput = getByPlaceholderText('filter');
const [visibilityContainerOne, visibilityContainerTwo] = getAllByTestId(
'visibility-container'
);

expect(visibilityContainerOne).toHaveStyle('opacity: 1');
expect(visibilityContainerTwo).toHaveStyle('opacity: 1');

fireEvent.change(filterInput, {
target: {
value: 'css',
},
});

expect(visibilityContainerOne).toHaveStyle('opacity: 1');
expect(visibilityContainerTwo).toHaveStyle('opacity: 0');
});

it('should display the trees when the filter query contains only spaces', () => {
const { getByPlaceholderText, getAllByTestId } = renderComponent([]);

const filterInput = getByPlaceholderText('filter');
const [visibilityContainerOne] = getAllByTestId('visibility-container');

expect(visibilityContainerOne).toHaveStyle('opacity: 1');

fireEvent.change(filterInput, {
target: {
value: ' ',
},
});

expect(visibilityContainerOne).toHaveStyle('opacity: 1');
});
});
});
2 changes: 1 addition & 1 deletion src/components/SkillNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function SkillNode({
return (
<SkillTreeSegment
key={child.id}
hasParent={true}
hasParent
parentPosition={parentPosition}
parentHasMultipleChildren={hasMultipleChildren}
shouldBeUnlocked={
Expand Down
17 changes: 17 additions & 0 deletions src/components/SkillProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { AppProvider } from '../context/AppContext';
import { FilterProvider } from '../context/FilterContext';

interface Props {
children: React.ReactNode;
}

function SkillProvider({ children }: Props) {
return (
<AppProvider>
<FilterProvider>{children}</FilterProvider>
</AppProvider>
);
}

export default SkillProvider;
Loading