Skip to content
This repository has been archived by the owner on Nov 27, 2022. It is now read-only.

Commit

Permalink
Add useWebId hook.
Browse files Browse the repository at this point in the history
  • Loading branch information
RubenVerborgh committed Feb 25, 2019
1 parent c6f4e84 commit 9fa96f8
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 60 deletions.
7 changes: 4 additions & 3 deletions src/components/LoggedIn.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import withWebId from './withWebId';
import useWebId from '../hooks/useWebId';

/** Pane that only shows its contents when the user is logged in. */
export default withWebId(function LoggedIn({ webId, children }) {
export default function LoggedIn({ children }) {
const webId = useWebId();
return webId && children || null;
});
}
7 changes: 4 additions & 3 deletions src/components/LoggedOut.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import withWebId from './withWebId';
import useWebId from '../hooks/useWebId';

/** Pane that only shows its contents when the user is logged out. */
export default withWebId(function LoggedOut({ webId, children }) {
export default function LoggedOut({ children }) {
const webId = useWebId();
return !webId && children || null;
});
}
36 changes: 4 additions & 32 deletions src/components/withWebId.jsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,10 @@
import React from 'react';
import auth from 'solid-auth-client';
import { getDisplayName } from '../util';

// Track all instances to inform them of WebID changes
const instances = new Set();
let authState = { webId: undefined };
import useWebId from '../hooks/useWebId';
import { higherOrderComponent } from '../util';

/**
* Higher-order component that passes the WebID of the logged-in user
* to the webId property of the wrapped component.
*/
export default function withWebId(Component) {
return class WithWebID extends React.Component {
static displayName = `WithWebId(${getDisplayName(Component)})`;

state = authState;

componentDidMount() {
instances.add(this);
}

componentWillUnmount() {
instances.delete(this);
}

render() {
return <Component webId={this.state.webId} {...this.props} />;
}
};
}

// Inform all instances when the WebID changes
auth.trackSession(session => {
authState = { webId: session && session.webId };
for (const instance of instances)
instance.setState(authState);
});
export default higherOrderComponent('WithWebId', Component =>
props => <Component {...props} webId={useWebId()} />);
29 changes: 29 additions & 0 deletions src/hooks/useWebId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState, useEffect } from 'react';
import auth from 'solid-auth-client';

// Keep track of the WebID and the state setters following it
let webId;
const setters = new Set();

/**
* Returns the WebID (string) of the active user,
* `null` if there is no user,
* or `undefined` if the user state is pending.
*/
export default function useWebId() {
const [, setWebId] = useState(webId);

useEffect(() => {
setters.add(setWebId);
return () => setters.delete(setWebId);
}, []);

return webId;
}

// Inform all setters when the WebID changes
auth.trackSession(session => {
webId = session && session.webId;
for (const setter of setters)
setter(webId);
});
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import useWebId from './hooks/useWebId';

import withWebId from './components/withWebId';
import evaluateExpressions from './components/evaluateExpressions';
import evaluateList from './components/evaluateList';
Expand All @@ -20,6 +22,8 @@ import List from './components/List';
import './prop-types';

export {
useWebId,

withWebId,
evaluateExpressions,
evaluateList,
Expand Down
11 changes: 11 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ export function getDisplayName(Component) {
return Component.displayName || Component.name || 'Component';
}

/**
* Creates a higher-order component with the given name.
*/
export function higherOrderComponent(name, createWrapper) {
return Component => {
const Wrapper = createWrapper(Component);
Wrapper.displayName = `${name}(${getDisplayName(Component)})`;
return Wrapper;
};
}

/**
* Creates a task queue that enforces a minimum time between tasks.
* Optionally, new tasks can cause any old ones to be dropped.
Expand Down
1 change: 1 addition & 0 deletions test/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
func-style: off,
new-cap: off,
no-empty-function: off,
no-void: off,
},
}
24 changes: 17 additions & 7 deletions test/components/LoggedIn-test.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react';
import { LoggedIn } from '../../src/';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import auth from 'solid-auth-client';

describe('A LoggedIn pane', () => {
let pane;
beforeEach(() => pane.update());

describe('with children', () => {
beforeAll(() => {
Expand All @@ -13,31 +15,39 @@ describe('A LoggedIn pane', () => {
afterAll(() => pane.unmount());

describe('when the user is not logged in', () => {
beforeAll(() => auth.mockWebId(null));
beforeAll(() => !act(() => {
auth.mockWebId(null);
}));

it('is empty', () => {
expect(pane.text()).toBe(null);
expect(pane.debug()).toBe('<LoggedIn />');
});
});

describe('when the user is logged in', () => {
beforeAll(() => auth.mockWebId('https://example.org/#me'));
beforeAll(() => !act(() => {
auth.mockWebId('https://example.org/#me');
}));

it('renders the content', () => {
expect(pane.text()).toBe('Logged in');
expect(pane.debug()).toMatch(/Logged in/);
});
});
});

describe('without children', () => {
beforeAll(() => (pane = mount(<LoggedIn/>)));
beforeAll(() => !act(() => {
pane = mount(<LoggedIn/>);
}));
afterAll(() => pane.unmount());

describe('when the user is logged in', () => {
beforeAll(() => auth.mockWebId('https://example.org/#me'));
beforeAll(() => !act(() => {
auth.mockWebId('https://example.org/#me');
}));

it('is empty', () => {
expect(pane.text()).toBe(null);
expect(pane.debug()).toBe('<LoggedIn />');
});
});
});
Expand Down
28 changes: 19 additions & 9 deletions test/components/LoggedOut-test.jsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,53 @@
import React from 'react';
import { LoggedOut } from '../../src/';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import auth from 'solid-auth-client';

describe('A LoggedOut pane', () => {
let pane;
beforeEach(() => pane.update());

describe('with children', () => {
beforeAll(() => {
beforeAll(() => !act(() => {
pane = mount(<LoggedOut>Logged out</LoggedOut>);
});
}));
afterAll(() => pane.unmount());

describe('when the user is not logged in', () => {
beforeAll(() => auth.mockWebId(null));
beforeAll(() => !act(() => {
auth.mockWebId(null);
}));

it('renders the content', () => {
expect(pane.text()).toBe('Logged out');
expect(pane.debug()).toMatch(/Logged out/);
});
});

describe('when the user is logged in', () => {
beforeAll(() => auth.mockWebId('https://example.org/#me'));
beforeAll(() => !act(() => {
auth.mockWebId('https://example.org/#me');
}));

it('is empty', () => {
expect(pane.text()).toBe(null);
expect(pane.debug()).toBe('<LoggedOut />');
});
});
});

describe('without children', () => {
beforeAll(() => (pane = mount(<LoggedOut/>)));
beforeAll(() => !act(() => {
pane = mount(<LoggedOut/>);
}));
afterAll(() => pane.unmount());

describe('when the user is not logged in', () => {
beforeAll(() => auth.mockWebId(null));
beforeAll(() => !act(() => {
auth.mockWebId(null);
}));

it('is empty', () => {
expect(pane.text()).toBe(null);
expect(pane.debug()).toBe('<LoggedOut />');
});
});
});
Expand Down
9 changes: 7 additions & 2 deletions test/components/Value-test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { Value } from '../../src/';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { mockPromise, update, setProps, timers } from '../util';
import data from '@solid/query-ldflex';
import auth from 'solid-auth-client';
Expand Down Expand Up @@ -146,7 +147,9 @@ describe('A Value', () => {
});

describe('after the user changes', () => {
beforeEach(() => auth.mockWebId('https://example.org/#me'));
beforeEach(() => !act(() => {
auth.mockWebId('https://example.org/#me');
}));

it('re-evaluates the expression', () => {
expect(data.resolve).toBeCalledTimes(2);
Expand All @@ -164,7 +167,9 @@ describe('A Value', () => {
afterEach(() => field.unmount());

describe('after the user changes', () => {
beforeEach(() => auth.mockWebId('https://example.org/#me'));
beforeEach(() => !act(() => {
auth.mockWebId('https://example.org/#me');
}));

it('does not re-evaluate the expression', () => {
expect(expression.then).toBeCalledTimes(1);
Expand Down
5 changes: 4 additions & 1 deletion test/components/evaluateExpressions-test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { evaluateExpressions } from '../../src/';
import { mount, render } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { mockPromise, setProps, timers } from '../util';
import data from '@solid/query-ldflex';
import auth from 'solid-auth-client';
Expand Down Expand Up @@ -333,7 +334,9 @@ describe('An evaluateExpressions wrapper', () => {
bar = mockPromise();
bar.resolve('second change');
data.resolve.mockReturnValue(bar);
auth.mockWebId('https://example.org/#me');
act(() => {
auth.mockWebId('https://example.org/#me');
});
wrapper.update();
});

Expand Down
13 changes: 10 additions & 3 deletions test/components/withWebId-test.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React from 'react';
import { withWebId } from '../../src/';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import auth from 'solid-auth-client';

describe('A withWebId wrapper', () => {
const Wrapper = withWebId(() => <span>contents</span>);
let wrapper;

beforeAll(() => (wrapper = mount(<Wrapper foo="bar"/>)));
beforeAll(() => !act(() => {
wrapper = mount(<Wrapper foo="bar"/>);
}));
beforeEach(() => wrapper.update());
afterAll(() => wrapper.unmount());

Expand All @@ -26,7 +29,9 @@ describe('A withWebId wrapper', () => {
});

describe('when the user is not logged in', () => {
beforeAll(() => auth.mockWebId(null));
beforeAll(() => !act(() => {
auth.mockWebId(null);
}));

it('renders the wrapped component', () => {
expect(wrapper.html()).toBe('<span>contents</span>');
Expand All @@ -42,7 +47,9 @@ describe('A withWebId wrapper', () => {
});

describe('when the user is logged in', () => {
beforeAll(() => auth.mockWebId('https://example.org/#me'));
beforeAll(() => !act(() => {
auth.mockWebId('https://example.org/#me');
}));

it('renders the wrapped component', () => {
expect(wrapper.html()).toBe('<span>contents</span>');
Expand Down
26 changes: 26 additions & 0 deletions test/hooks/useWebId-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useWebId } from '../../src/';
import { renderHook, cleanup, act } from 'react-hooks-testing-library';
import auth from 'solid-auth-client';

describe('useWebId', () => {
let result, unmount;
beforeAll(() => {
({ result, unmount } = renderHook(() => useWebId()));
});
afterAll(() => unmount());
afterAll(cleanup);

it('returns undefined when the login status is unknown', () => {
expect(result.current).toBeUndefined();
});

it('returns null when the user is logged out', () => {
act(() => void auth.mockWebId(null));
expect(result.current).toBeNull();
});

it('returns the WebID when the user is logged in', () => {
act(() => void auth.mockWebId('https://example.org/#me'));
expect(result.current).toBe('https://example.org/#me');
});
});
1 change: 1 addition & 0 deletions test/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('The SolidReactComponents module', () => {
'Label',
'Name',
'List',
'useWebId',
];

exports.forEach(name => {
Expand Down

0 comments on commit 9fa96f8

Please sign in to comment.