mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-28 20:20:35 +00:00
Remove the repository select from explorer/ (#988)
Historically, a single cred explorer instance could load many different repositories. This turned out to be an anti-feature: we'd rather have a particular url hardlink to exploring the cred for a particular project. This commit removes the repository select from the explorer, and instead mandates that the explorer always has the RepoId passed down from above. Besides providing a better UX, this also greatly simplifies the logic for the explorer, since we no longer have an "initializing state" that doesn't have any RepoId. This builds on the work in #984, and swaps out the old "prototype" page (which has been rendered non-functional by this change) for the new "prototypes" page. Note that it stays at the same route, so links to sourcecred.io/prototype will continue to function. Test plan: Ran `yarn test --full`, and verified that `yarn start` produces a working site.
This commit is contained in:
parent
738853cd02
commit
29065f44d6
@ -7,23 +7,24 @@ import type {LocalStore} from "../webutil/localStore";
|
|||||||
import CheckedLocalStore from "../webutil/checkedLocalStore";
|
import CheckedLocalStore from "../webutil/checkedLocalStore";
|
||||||
import BrowserLocalStore from "../webutil/browserLocalStore";
|
import BrowserLocalStore from "../webutil/browserLocalStore";
|
||||||
import Link from "../webutil/Link";
|
import Link from "../webutil/Link";
|
||||||
|
import type {RepoId} from "../core/repoId";
|
||||||
|
|
||||||
import {PagerankTable} from "./pagerankTable/Table";
|
import {PagerankTable} from "./pagerankTable/Table";
|
||||||
import type {WeightedTypes} from "../analysis/weights";
|
import type {WeightedTypes} from "../analysis/weights";
|
||||||
import {defaultWeightsForAdapterSet} from "./weights/weights";
|
import {defaultWeightsForAdapterSet} from "./weights/weights";
|
||||||
import RepositorySelect from "./RepositorySelect";
|
|
||||||
import {Prefix as GithubPrefix} from "../plugins/github/nodes";
|
import {Prefix as GithubPrefix} from "../plugins/github/nodes";
|
||||||
import {
|
import {
|
||||||
createStateTransitionMachine,
|
createStateTransitionMachine,
|
||||||
type AppState,
|
type AppState,
|
||||||
type StateTransitionMachineInterface,
|
type StateTransitionMachineInterface,
|
||||||
uninitializedState,
|
initialState,
|
||||||
} from "./state";
|
} from "./state";
|
||||||
import {StaticAdapterSet} from "./adapters/adapterSet";
|
import {StaticAdapterSet} from "./adapters/adapterSet";
|
||||||
|
|
||||||
export class AppPage extends React.Component<{|
|
export class AppPage extends React.Component<{|
|
||||||
+assets: Assets,
|
+assets: Assets,
|
||||||
+adapters: StaticAdapterSet,
|
+adapters: StaticAdapterSet,
|
||||||
|
+repoId: RepoId,
|
||||||
|}> {
|
|}> {
|
||||||
static _LOCAL_STORE = new CheckedLocalStore(
|
static _LOCAL_STORE = new CheckedLocalStore(
|
||||||
new BrowserLocalStore({
|
new BrowserLocalStore({
|
||||||
@ -36,6 +37,7 @@ export class AppPage extends React.Component<{|
|
|||||||
const App = createApp(createStateTransitionMachine);
|
const App = createApp(createStateTransitionMachine);
|
||||||
return (
|
return (
|
||||||
<App
|
<App
|
||||||
|
repoId={this.props.repoId}
|
||||||
assets={this.props.assets}
|
assets={this.props.assets}
|
||||||
adapters={this.props.adapters}
|
adapters={this.props.adapters}
|
||||||
localStore={AppPage._LOCAL_STORE}
|
localStore={AppPage._LOCAL_STORE}
|
||||||
@ -48,6 +50,7 @@ type Props = {|
|
|||||||
+assets: Assets,
|
+assets: Assets,
|
||||||
+localStore: LocalStore,
|
+localStore: LocalStore,
|
||||||
+adapters: StaticAdapterSet,
|
+adapters: StaticAdapterSet,
|
||||||
|
+repoId: RepoId,
|
||||||
|};
|
|};
|
||||||
type State = {|
|
type State = {|
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
@ -66,7 +69,7 @@ export function createApp(
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
appState: uninitializedState(),
|
appState: initialState(this.props.repoId),
|
||||||
weightedTypes: defaultWeightsForAdapterSet(props.adapters),
|
weightedTypes: defaultWeightsForAdapterSet(props.adapters),
|
||||||
};
|
};
|
||||||
this.stateTransitionMachine = createSTM(
|
this.stateTransitionMachine = createSTM(
|
||||||
@ -76,7 +79,6 @@ export function createApp(
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {localStore} = this.props;
|
|
||||||
const {appState} = this.state;
|
const {appState} = this.state;
|
||||||
let pagerankTable;
|
let pagerankTable;
|
||||||
if (appState.type === "PAGERANK_EVALUATED") {
|
if (appState.type === "PAGERANK_EVALUATED") {
|
||||||
@ -109,15 +111,6 @@ export function createApp(
|
|||||||
feedback
|
feedback
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
<div style={{marginBottom: 10}}>
|
|
||||||
<RepositorySelect
|
|
||||||
assets={this.props.assets}
|
|
||||||
localStore={localStore}
|
|
||||||
onChange={(repoId) =>
|
|
||||||
this.stateTransitionMachine.setRepoId(repoId)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
disabled={
|
disabled={
|
||||||
appState.type === "UNINITIALIZED" ||
|
appState.type === "UNINITIALIZED" ||
|
||||||
@ -154,9 +147,6 @@ export class LoadingIndicator extends React.PureComponent<{|
|
|||||||
|
|
||||||
export function loadingText(state: AppState) {
|
export function loadingText(state: AppState) {
|
||||||
switch (state.type) {
|
switch (state.type) {
|
||||||
case "UNINITIALIZED": {
|
|
||||||
return "Initializing...";
|
|
||||||
}
|
|
||||||
case "READY_TO_LOAD_GRAPH": {
|
case "READY_TO_LOAD_GRAPH": {
|
||||||
return {
|
return {
|
||||||
LOADING: "Loading graph...",
|
LOADING: "Loading graph...",
|
||||||
|
@ -11,10 +11,8 @@ import {DynamicAdapterSet, StaticAdapterSet} from "./adapters/adapterSet";
|
|||||||
import {FactorioStaticAdapter} from "../plugins/demo/appAdapter";
|
import {FactorioStaticAdapter} from "../plugins/demo/appAdapter";
|
||||||
import {defaultWeightsForAdapter} from "./weights/weights";
|
import {defaultWeightsForAdapter} from "./weights/weights";
|
||||||
|
|
||||||
import RepositorySelect from "./RepositorySelect";
|
|
||||||
import {PagerankTable} from "./pagerankTable/Table";
|
import {PagerankTable} from "./pagerankTable/Table";
|
||||||
import {createApp, LoadingIndicator} from "./App";
|
import {createApp, LoadingIndicator} from "./App";
|
||||||
import {uninitializedState} from "./state";
|
|
||||||
import {Prefix as GithubPrefix} from "../plugins/github/nodes";
|
import {Prefix as GithubPrefix} from "../plugins/github/nodes";
|
||||||
|
|
||||||
require("../webutil/testUtil").configureEnzyme();
|
require("../webutil/testUtil").configureEnzyme();
|
||||||
@ -22,7 +20,6 @@ require("../webutil/testUtil").configureEnzyme();
|
|||||||
describe("explorer/App", () => {
|
describe("explorer/App", () => {
|
||||||
function example() {
|
function example() {
|
||||||
let setState, getState;
|
let setState, getState;
|
||||||
const setRepoId = jest.fn();
|
|
||||||
const loadGraph = jest.fn();
|
const loadGraph = jest.fn();
|
||||||
const runPagerank = jest.fn();
|
const runPagerank = jest.fn();
|
||||||
const loadGraphAndRunPagerank = jest.fn();
|
const loadGraphAndRunPagerank = jest.fn();
|
||||||
@ -31,7 +28,6 @@ describe("explorer/App", () => {
|
|||||||
setState = _setState;
|
setState = _setState;
|
||||||
getState = _getState;
|
getState = _getState;
|
||||||
return {
|
return {
|
||||||
setRepoId,
|
|
||||||
loadGraph,
|
loadGraph,
|
||||||
runPagerank,
|
runPagerank,
|
||||||
loadGraphAndRunPagerank,
|
loadGraphAndRunPagerank,
|
||||||
@ -43,6 +39,7 @@ describe("explorer/App", () => {
|
|||||||
assets={new Assets("/foo/")}
|
assets={new Assets("/foo/")}
|
||||||
adapters={new StaticAdapterSet([])}
|
adapters={new StaticAdapterSet([])}
|
||||||
localStore={localStore}
|
localStore={localStore}
|
||||||
|
repoId={makeRepoId("foo", "bar")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if (setState == null || getState == null) {
|
if (setState == null || getState == null) {
|
||||||
@ -52,7 +49,6 @@ describe("explorer/App", () => {
|
|||||||
el,
|
el,
|
||||||
setState,
|
setState,
|
||||||
getState,
|
getState,
|
||||||
setRepoId,
|
|
||||||
loadGraph,
|
loadGraph,
|
||||||
runPagerank,
|
runPagerank,
|
||||||
loadGraphAndRunPagerank,
|
loadGraphAndRunPagerank,
|
||||||
@ -62,7 +58,6 @@ describe("explorer/App", () => {
|
|||||||
|
|
||||||
const emptyAdapters = new DynamicAdapterSet(new StaticAdapterSet([]), []);
|
const emptyAdapters = new DynamicAdapterSet(new StaticAdapterSet([]), []);
|
||||||
const exampleStates = {
|
const exampleStates = {
|
||||||
uninitialized: uninitializedState,
|
|
||||||
readyToLoadGraph: (loadingState) => {
|
readyToLoadGraph: (loadingState) => {
|
||||||
return () => ({
|
return () => ({
|
||||||
type: "READY_TO_LOAD_GRAPH",
|
type: "READY_TO_LOAD_GRAPH",
|
||||||
@ -95,8 +90,7 @@ describe("explorer/App", () => {
|
|||||||
});
|
});
|
||||||
it("setState is wired properly", () => {
|
it("setState is wired properly", () => {
|
||||||
const {setState, el} = example();
|
const {setState, el} = example();
|
||||||
expect(uninitializedState()).not.toBe(uninitializedState()); // sanity check
|
const newState = exampleStates.readyToLoadGraph("LOADING")();
|
||||||
const newState = uninitializedState();
|
|
||||||
setState(newState);
|
setState(newState);
|
||||||
expect(el.state().appState).toBe(newState);
|
expect(el.state().appState).toBe(newState);
|
||||||
});
|
});
|
||||||
@ -118,18 +112,6 @@ describe("explorer/App", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when in state:", () => {
|
describe("when in state:", () => {
|
||||||
function testRepositorySelect(stateFn) {
|
|
||||||
it("creates a working RepositorySelect", () => {
|
|
||||||
const {el, setRepoId, setState, localStore} = example();
|
|
||||||
setState(stateFn());
|
|
||||||
const rs = el.find(RepositorySelect);
|
|
||||||
const newRepoId = makeRepoId("zoo", "zod");
|
|
||||||
rs.props().onChange(newRepoId);
|
|
||||||
expect(setRepoId).toHaveBeenCalledWith(newRepoId);
|
|
||||||
expect(rs.props().localStore).toBe(localStore);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function testAnalyzeCredButton(stateFn, {disabled}) {
|
function testAnalyzeCredButton(stateFn, {disabled}) {
|
||||||
const adjective = disabled ? "disabled" : "working";
|
const adjective = disabled ? "disabled" : "working";
|
||||||
it(`has a ${adjective} analyze cred button`, () => {
|
it(`has a ${adjective} analyze cred button`, () => {
|
||||||
@ -203,17 +185,12 @@ describe("explorer/App", () => {
|
|||||||
{analyzeCredDisabled, hasPagerankTable}
|
{analyzeCredDisabled, hasPagerankTable}
|
||||||
) {
|
) {
|
||||||
describe(suiteName, () => {
|
describe(suiteName, () => {
|
||||||
testRepositorySelect(stateFn);
|
|
||||||
testAnalyzeCredButton(stateFn, {disabled: analyzeCredDisabled});
|
testAnalyzeCredButton(stateFn, {disabled: analyzeCredDisabled});
|
||||||
testPagerankTable(stateFn, hasPagerankTable);
|
testPagerankTable(stateFn, hasPagerankTable);
|
||||||
testLoadingIndicator(stateFn);
|
testLoadingIndicator(stateFn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
stateTestSuite("UNINITIALIZED", exampleStates.uninitialized, {
|
|
||||||
analyzeCredDisabled: true,
|
|
||||||
hasPagerankTable: false,
|
|
||||||
});
|
|
||||||
describe("READY_TO_LOAD_GRAPH", () => {
|
describe("READY_TO_LOAD_GRAPH", () => {
|
||||||
for (const loadingState of ["LOADING", "NOT_LOADING", "FAILED"]) {
|
for (const loadingState of ["LOADING", "NOT_LOADING", "FAILED"]) {
|
||||||
stateTestSuite(
|
stateTestSuite(
|
||||||
@ -262,11 +239,6 @@ describe("explorer/App", () => {
|
|||||||
expect(el.text()).toEqual(expectedText);
|
expect(el.text()).toEqual(expectedText);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
testStatusText(
|
|
||||||
"initializing",
|
|
||||||
exampleStates.uninitialized,
|
|
||||||
"Initializing..."
|
|
||||||
);
|
|
||||||
testStatusText(
|
testStatusText(
|
||||||
"ready to load graph",
|
"ready to load graph",
|
||||||
exampleStates.readyToLoadGraph("NOT_LOADING"),
|
exampleStates.readyToLoadGraph("NOT_LOADING"),
|
||||||
|
@ -1,190 +0,0 @@
|
|||||||
// @flow
|
|
||||||
|
|
||||||
import React, {type Node} from "react";
|
|
||||||
import sortBy from "lodash.sortby";
|
|
||||||
import deepEqual from "lodash.isequal";
|
|
||||||
|
|
||||||
import * as NullUtil from "../util/null";
|
|
||||||
import type {LocalStore} from "../webutil/localStore";
|
|
||||||
import type {Assets} from "../webutil/assets";
|
|
||||||
|
|
||||||
import {fromJSON, REPO_ID_REGISTRY_API} from "./repoIdRegistry";
|
|
||||||
import {type RepoId, stringToRepoId, repoIdToString} from "../core/repoId";
|
|
||||||
export const REPO_ID_KEY = "selectedRepository";
|
|
||||||
|
|
||||||
export type Status =
|
|
||||||
| {|+type: "LOADING"|}
|
|
||||||
| {|
|
|
||||||
+type: "VALID",
|
|
||||||
+availableRepoIds: $ReadOnlyArray<RepoId>,
|
|
||||||
+selectedRepoId: RepoId,
|
|
||||||
|}
|
|
||||||
| {|+type: "NO_REPOS"|}
|
|
||||||
| {|+type: "FAILURE"|};
|
|
||||||
|
|
||||||
type Props = {|
|
|
||||||
+assets: Assets,
|
|
||||||
+onChange: (x: RepoId) => void,
|
|
||||||
+localStore: LocalStore,
|
|
||||||
|};
|
|
||||||
type State = {|status: Status|};
|
|
||||||
export default class RepositorySelect extends React.Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
status: {type: "LOADING"},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const {assets, localStore} = this.props;
|
|
||||||
loadStatus(assets, localStore).then((status) => {
|
|
||||||
this.setState({status});
|
|
||||||
if (status.type === "VALID") {
|
|
||||||
this.props.onChange(status.selectedRepoId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange(selectedRepoId: RepoId) {
|
|
||||||
const status = this.state.status;
|
|
||||||
if (status.type === "VALID") {
|
|
||||||
const newStatus = {...status, selectedRepoId};
|
|
||||||
this.setState({status: newStatus});
|
|
||||||
}
|
|
||||||
this.props.onChange(selectedRepoId);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<LocalStoreRepositorySelect
|
|
||||||
onChange={(selectedRepoId) => this.onChange(selectedRepoId)}
|
|
||||||
status={this.state.status}
|
|
||||||
localStore={this.props.localStore}
|
|
||||||
>
|
|
||||||
{({status, onChange}) => (
|
|
||||||
<PureRepositorySelect onChange={onChange} status={status} />
|
|
||||||
)}
|
|
||||||
</LocalStoreRepositorySelect>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadStatus(
|
|
||||||
assets: Assets,
|
|
||||||
localStore: LocalStore
|
|
||||||
): Promise<Status> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(assets.resolve(REPO_ID_REGISTRY_API));
|
|
||||||
if (response.status === 404) {
|
|
||||||
return {type: "NO_REPOS"};
|
|
||||||
}
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error(response);
|
|
||||||
return {type: "FAILURE"};
|
|
||||||
}
|
|
||||||
const json = await response.json();
|
|
||||||
const availableRepoIds = fromJSON(json);
|
|
||||||
if (availableRepoIds.length === 0) {
|
|
||||||
return {type: "NO_REPOS"};
|
|
||||||
}
|
|
||||||
const localStoreRepoId = localStore.get(REPO_ID_KEY, null);
|
|
||||||
const selectedRepoId = NullUtil.orElse(
|
|
||||||
availableRepoIds.find((x) => deepEqual(x, localStoreRepoId)),
|
|
||||||
availableRepoIds[availableRepoIds.length - 1]
|
|
||||||
);
|
|
||||||
const sortedRepoIds = sortBy(
|
|
||||||
availableRepoIds,
|
|
||||||
(r) => r.owner,
|
|
||||||
(r) => r.name
|
|
||||||
);
|
|
||||||
return {type: "VALID", availableRepoIds: sortedRepoIds, selectedRepoId};
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return {type: "FAILURE"};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LocalStoreRepositorySelect extends React.Component<{|
|
|
||||||
+status: Status,
|
|
||||||
+onChange: (repoId: RepoId) => void,
|
|
||||||
+localStore: LocalStore,
|
|
||||||
+children: ({
|
|
||||||
status: Status,
|
|
||||||
onChange: (selectedRepoId: RepoId) => void,
|
|
||||||
}) => Node,
|
|
||||||
|}> {
|
|
||||||
render() {
|
|
||||||
return this.props.children({
|
|
||||||
status: this.props.status,
|
|
||||||
onChange: (repoId) => {
|
|
||||||
this.props.onChange(repoId);
|
|
||||||
this.props.localStore.set(REPO_ID_KEY, repoId);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PureRepositorySelectProps = {|
|
|
||||||
+onChange: (x: RepoId) => void,
|
|
||||||
+status: Status,
|
|
||||||
|};
|
|
||||||
export class PureRepositorySelect extends React.PureComponent<
|
|
||||||
PureRepositorySelectProps
|
|
||||||
> {
|
|
||||||
renderSelect(
|
|
||||||
availableRepoIds: $ReadOnlyArray<RepoId>,
|
|
||||||
selectedRepoId: ?RepoId
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<label>
|
|
||||||
<span>Please choose a repository to inspect:</span>{" "}
|
|
||||||
{selectedRepoId != null && (
|
|
||||||
<select
|
|
||||||
value={repoIdToString(selectedRepoId)}
|
|
||||||
onChange={(e) => {
|
|
||||||
const repoId = stringToRepoId(e.target.value);
|
|
||||||
this.props.onChange(repoId);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{availableRepoIds.map((repoId) => {
|
|
||||||
const repoIdString = repoIdToString(repoId);
|
|
||||||
return (
|
|
||||||
<option value={repoIdString} key={repoIdString}>
|
|
||||||
{repoIdString}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderError(text: string) {
|
|
||||||
return <span style={{fontWeight: "bold", color: "red"}}>{text}</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {status} = this.props;
|
|
||||||
switch (status.type) {
|
|
||||||
case "LOADING":
|
|
||||||
// Just show an empty select while we wait.
|
|
||||||
return this.renderSelect([], null);
|
|
||||||
case "VALID":
|
|
||||||
return this.renderSelect(
|
|
||||||
status.availableRepoIds,
|
|
||||||
status.selectedRepoId
|
|
||||||
);
|
|
||||||
case "NO_REPOS":
|
|
||||||
return this.renderError("Error: No repositories found.");
|
|
||||||
case "FAILURE":
|
|
||||||
return this.renderError(
|
|
||||||
"Error: Unable to load repository registry. " +
|
|
||||||
"See console for details."
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw new Error((status.type: empty));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,411 +0,0 @@
|
|||||||
// @flow
|
|
||||||
import React from "react";
|
|
||||||
import {shallow, mount} from "enzyme";
|
|
||||||
|
|
||||||
import * as NullUtil from "../util/null";
|
|
||||||
import testLocalStore from "../webutil/testLocalStore";
|
|
||||||
import RepositorySelect, {
|
|
||||||
PureRepositorySelect,
|
|
||||||
LocalStoreRepositorySelect,
|
|
||||||
loadStatus,
|
|
||||||
type Status,
|
|
||||||
REPO_ID_KEY,
|
|
||||||
} from "./RepositorySelect";
|
|
||||||
import {Assets} from "../webutil/assets";
|
|
||||||
|
|
||||||
import {
|
|
||||||
toJSON,
|
|
||||||
type RepoIdRegistry,
|
|
||||||
REPO_ID_REGISTRY_API,
|
|
||||||
} from "./repoIdRegistry";
|
|
||||||
import {makeRepoId} from "../core/repoId";
|
|
||||||
|
|
||||||
require("../webutil/testUtil").configureEnzyme();
|
|
||||||
require("../webutil/testUtil").configureAphrodite();
|
|
||||||
|
|
||||||
describe("explorer/RepositorySelect", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
fetch.resetMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
function mockRegistry(registry: RepoIdRegistry) {
|
|
||||||
fetch.mockResponseOnce(JSON.stringify(toJSON(registry)));
|
|
||||||
}
|
|
||||||
describe("PureRepositorySelect", () => {
|
|
||||||
it("doesn't render a select while loading", () => {
|
|
||||||
const e = shallow(
|
|
||||||
<PureRepositorySelect status={{type: "LOADING"}} onChange={jest.fn()} />
|
|
||||||
);
|
|
||||||
const span = e.find("span");
|
|
||||||
expect(span.text()).toBe("Please choose a repository to inspect:");
|
|
||||||
const select = e.find("select");
|
|
||||||
expect(select).toHaveLength(0);
|
|
||||||
});
|
|
||||||
it("renders an error message if no repositories are available", () => {
|
|
||||||
const e = shallow(
|
|
||||||
<PureRepositorySelect
|
|
||||||
status={{type: "NO_REPOS"}}
|
|
||||||
onChange={jest.fn()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const span = e.find("span");
|
|
||||||
expect(span.text()).toBe("Error: No repositories found.");
|
|
||||||
});
|
|
||||||
it("renders an error message if there was an error while loading", () => {
|
|
||||||
const e = shallow(
|
|
||||||
<PureRepositorySelect status={{type: "FAILURE"}} onChange={jest.fn()} />
|
|
||||||
);
|
|
||||||
const span = e.find("span");
|
|
||||||
expect(span.text()).toBe(
|
|
||||||
"Error: Unable to load repository registry. See console for details."
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("renders a select with all available repoIds as options", () => {
|
|
||||||
const availableRepoIds = [
|
|
||||||
makeRepoId("foo", "bar"),
|
|
||||||
makeRepoId("zod", "zoink"),
|
|
||||||
];
|
|
||||||
const selectedRepoId = availableRepoIds[0];
|
|
||||||
const e = shallow(
|
|
||||||
<PureRepositorySelect
|
|
||||||
status={{type: "VALID", availableRepoIds, selectedRepoId}}
|
|
||||||
onChange={jest.fn()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const options = e.find("option");
|
|
||||||
expect(options.map((x) => x.text())).toEqual(["foo/bar", "zod/zoink"]);
|
|
||||||
});
|
|
||||||
it("the selectedRepoId is selected", () => {
|
|
||||||
const availableRepoIds = [
|
|
||||||
makeRepoId("foo", "bar"),
|
|
||||||
makeRepoId("zod", "zoink"),
|
|
||||||
];
|
|
||||||
const selectedRepoId = availableRepoIds[0];
|
|
||||||
const e = shallow(
|
|
||||||
<PureRepositorySelect
|
|
||||||
status={{type: "VALID", availableRepoIds, selectedRepoId}}
|
|
||||||
onChange={jest.fn()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(e.find("select").prop("value")).toBe("foo/bar");
|
|
||||||
});
|
|
||||||
it("clicking an option triggers the onChange", () => {
|
|
||||||
const availableRepoIds = [
|
|
||||||
makeRepoId("foo", "bar"),
|
|
||||||
makeRepoId("zod", "zoink"),
|
|
||||||
];
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const e = shallow(
|
|
||||||
<PureRepositorySelect
|
|
||||||
status={{
|
|
||||||
type: "VALID",
|
|
||||||
availableRepoIds,
|
|
||||||
selectedRepoId: availableRepoIds[0],
|
|
||||||
}}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
e.find("select").simulate("change", {target: {value: "zod/zoink"}});
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(1);
|
|
||||||
expect(onChange).toHaveBeenLastCalledWith(availableRepoIds[1]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("loadStatus", () => {
|
|
||||||
const assets = new Assets("/my/gateway/");
|
|
||||||
function expectLoadValidStatus(
|
|
||||||
localStore,
|
|
||||||
expectedAvailableRepoIds,
|
|
||||||
expectedSelectedRepoId
|
|
||||||
) {
|
|
||||||
const result = loadStatus(assets, localStore);
|
|
||||||
expect(fetch).toHaveBeenCalledTimes(1);
|
|
||||||
expect(fetch).toHaveBeenCalledWith("/my/gateway" + REPO_ID_REGISTRY_API);
|
|
||||||
expect.assertions(7);
|
|
||||||
return result.then((status) => {
|
|
||||||
expect(status.type).toBe("VALID");
|
|
||||||
if (status.type !== "VALID") {
|
|
||||||
throw new Error("Impossible");
|
|
||||||
}
|
|
||||||
expect(status.availableRepoIds).toEqual(expectedAvailableRepoIds);
|
|
||||||
expect(status.selectedRepoId).toEqual(expectedSelectedRepoId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
it("calls fetch and handles a simple success", () => {
|
|
||||||
const repoId = makeRepoId("foo", "bar");
|
|
||||||
mockRegistry([repoId]);
|
|
||||||
return expectLoadValidStatus(testLocalStore(), [repoId], repoId);
|
|
||||||
});
|
|
||||||
it("returns repoIds in sorted order, and selects the last repoId", () => {
|
|
||||||
const repoIds = [
|
|
||||||
makeRepoId("a", "b"),
|
|
||||||
makeRepoId("a", "z"),
|
|
||||||
makeRepoId("foo", "bar"),
|
|
||||||
];
|
|
||||||
const nonSortedRepoIds = [repoIds[2], repoIds[0], repoIds[1]];
|
|
||||||
mockRegistry(nonSortedRepoIds);
|
|
||||||
return expectLoadValidStatus(testLocalStore(), repoIds, repoIds[1]);
|
|
||||||
});
|
|
||||||
it("returns FAILURE on invalid fetch response", () => {
|
|
||||||
fetch.mockResponseOnce(JSON.stringify(["hello"]));
|
|
||||||
expect.assertions(4);
|
|
||||||
return loadStatus(assets, testLocalStore()).then((status) => {
|
|
||||||
expect(status).toEqual({type: "FAILURE"});
|
|
||||||
expect(console.error).toHaveBeenCalledTimes(1);
|
|
||||||
// $ExpectFlowError
|
|
||||||
console.error = jest.fn();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("returns FAILURE on fetch failure", () => {
|
|
||||||
fetch.mockReject(new Error("some failure"));
|
|
||||||
expect.assertions(4);
|
|
||||||
return loadStatus(assets, testLocalStore()).then((status) => {
|
|
||||||
expect(status).toEqual({type: "FAILURE"});
|
|
||||||
expect(console.error).toHaveBeenCalledTimes(1);
|
|
||||||
// $ExpectFlowError
|
|
||||||
console.error = jest.fn();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("returns NO_REPOS on fetch 404", () => {
|
|
||||||
fetch.mockResponseOnce("irrelevant", {status: 404});
|
|
||||||
expect.assertions(3);
|
|
||||||
return loadStatus(assets, testLocalStore()).then((status) => {
|
|
||||||
expect(status).toEqual({type: "NO_REPOS"});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("loads selectedRepoId from localStore, if available", () => {
|
|
||||||
const repoIds = [
|
|
||||||
makeRepoId("a", "b"),
|
|
||||||
makeRepoId("a", "z"),
|
|
||||||
makeRepoId("foo", "bar"),
|
|
||||||
];
|
|
||||||
mockRegistry(repoIds);
|
|
||||||
const localStore = testLocalStore();
|
|
||||||
localStore.set(REPO_ID_KEY, {owner: "a", name: "z"});
|
|
||||||
return expectLoadValidStatus(localStore, repoIds, repoIds[1]);
|
|
||||||
});
|
|
||||||
it("ignores selectedRepoId from localStore, if not available", () => {
|
|
||||||
const repoIds = [
|
|
||||||
makeRepoId("a", "b"),
|
|
||||||
makeRepoId("a", "z"),
|
|
||||||
makeRepoId("foo", "bar"),
|
|
||||||
];
|
|
||||||
mockRegistry(repoIds);
|
|
||||||
const localStore = testLocalStore();
|
|
||||||
localStore.set(REPO_ID_KEY, {owner: "non", name: "existent"});
|
|
||||||
return expectLoadValidStatus(localStore, repoIds, repoIds[2]);
|
|
||||||
});
|
|
||||||
it("ignores malformed value in localStore", () => {
|
|
||||||
const repoIds = [
|
|
||||||
makeRepoId("a", "b"),
|
|
||||||
makeRepoId("a", "z"),
|
|
||||||
makeRepoId("foo", "bar"),
|
|
||||||
];
|
|
||||||
mockRegistry(repoIds);
|
|
||||||
const localStore = testLocalStore();
|
|
||||||
localStore.set(REPO_ID_KEY, 42);
|
|
||||||
return expectLoadValidStatus(localStore, repoIds, repoIds[2]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("LocalStoreRepositorySelect", () => {
|
|
||||||
it("instantiates the child component", () => {
|
|
||||||
const status = {type: "LOADING"};
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const e = shallow(
|
|
||||||
<LocalStoreRepositorySelect
|
|
||||||
onChange={onChange}
|
|
||||||
status={status}
|
|
||||||
localStore={testLocalStore()}
|
|
||||||
>
|
|
||||||
{({status, onChange}) => (
|
|
||||||
<PureRepositorySelect status={status} onChange={onChange} />
|
|
||||||
)}
|
|
||||||
</LocalStoreRepositorySelect>
|
|
||||||
);
|
|
||||||
const child = e.find("PureRepositorySelect");
|
|
||||||
expect(child.props().status).toEqual(status);
|
|
||||||
});
|
|
||||||
it("passes onChange result up to parent", () => {
|
|
||||||
const status = {type: "LOADING"};
|
|
||||||
const onChange = jest.fn();
|
|
||||||
let childOnChange;
|
|
||||||
shallow(
|
|
||||||
<LocalStoreRepositorySelect
|
|
||||||
onChange={onChange}
|
|
||||||
status={status}
|
|
||||||
localStore={testLocalStore()}
|
|
||||||
>
|
|
||||||
{({status, onChange}) => {
|
|
||||||
childOnChange = onChange;
|
|
||||||
return <PureRepositorySelect status={status} onChange={onChange} />;
|
|
||||||
}}
|
|
||||||
</LocalStoreRepositorySelect>
|
|
||||||
);
|
|
||||||
const repoId = {owner: "foo", name: "bar"};
|
|
||||||
NullUtil.get(childOnChange)(repoId);
|
|
||||||
expect(onChange).toHaveBeenCalledWith(repoId);
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
it("stores onChange result in localStore", () => {
|
|
||||||
const status = {type: "LOADING"};
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const localStore = testLocalStore();
|
|
||||||
let childOnChange;
|
|
||||||
shallow(
|
|
||||||
<LocalStoreRepositorySelect
|
|
||||||
onChange={onChange}
|
|
||||||
status={status}
|
|
||||||
localStore={localStore}
|
|
||||||
>
|
|
||||||
{({status, onChange}) => {
|
|
||||||
childOnChange = onChange;
|
|
||||||
return <PureRepositorySelect status={status} onChange={onChange} />;
|
|
||||||
}}
|
|
||||||
</LocalStoreRepositorySelect>
|
|
||||||
);
|
|
||||||
const repoId = {owner: "foo", name: "bar"};
|
|
||||||
NullUtil.get(childOnChange)(repoId);
|
|
||||||
expect(localStore.get(REPO_ID_KEY)).toEqual(repoId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("RepositorySelect", () => {
|
|
||||||
const assets = new Assets("/my/gateway/");
|
|
||||||
|
|
||||||
it("calls `loadStatus` with the proper assets", () => {
|
|
||||||
mockRegistry([makeRepoId("irrelevant", "unused")]);
|
|
||||||
shallow(
|
|
||||||
<RepositorySelect
|
|
||||||
assets={assets}
|
|
||||||
onChange={jest.fn()}
|
|
||||||
localStore={testLocalStore()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
// A bit of overlap with tests for `loadStatus` directly---it'd be
|
|
||||||
// nicer to spy on `loadStatus`, but that's at module top level,
|
|
||||||
// so `RepositorySelect` closes over it directly.
|
|
||||||
expect(fetch).toHaveBeenCalledWith("/my/gateway" + REPO_ID_REGISTRY_API);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("initially renders a LocalStoreRepositorySelect with status LOADING", () => {
|
|
||||||
mockRegistry([makeRepoId("irrelevant", "unused")]);
|
|
||||||
const e = shallow(
|
|
||||||
<RepositorySelect
|
|
||||||
assets={assets}
|
|
||||||
onChange={jest.fn()}
|
|
||||||
localStore={testLocalStore()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const child = e.find(LocalStoreRepositorySelect);
|
|
||||||
const status = child.props().status;
|
|
||||||
const onChange = jest.fn();
|
|
||||||
expect(status).toEqual({type: "LOADING"});
|
|
||||||
const grandChild = child.props().children({status, onChange});
|
|
||||||
expect(grandChild.type).toBe(PureRepositorySelect);
|
|
||||||
});
|
|
||||||
|
|
||||||
function waitForUpdate(enzymeWrapper) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setImmediate(() => {
|
|
||||||
enzymeWrapper.update();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
it("on successful load, sets the status on the child", async () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const selectedRepoId = makeRepoId("foo", "bar");
|
|
||||||
mockRegistry([selectedRepoId]);
|
|
||||||
const e = shallow(
|
|
||||||
<RepositorySelect
|
|
||||||
assets={assets}
|
|
||||||
onChange={onChange}
|
|
||||||
localStore={testLocalStore()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
await waitForUpdate(e);
|
|
||||||
const childStatus = e.props().status;
|
|
||||||
const availableRepoIds = [selectedRepoId];
|
|
||||||
expect(childStatus).toEqual({
|
|
||||||
type: "VALID",
|
|
||||||
selectedRepoId,
|
|
||||||
availableRepoIds,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("on successful load, passes the status to the onChange", async () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const repoId = makeRepoId("foo", "bar");
|
|
||||||
mockRegistry([repoId]);
|
|
||||||
const e = shallow(
|
|
||||||
<RepositorySelect
|
|
||||||
assets={assets}
|
|
||||||
onChange={onChange}
|
|
||||||
localStore={testLocalStore()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
await waitForUpdate(e);
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(1);
|
|
||||||
expect(onChange).toHaveBeenCalledWith(repoId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("on failed load, onChange not called", async () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
fetch.mockReject(new Error("something bad"));
|
|
||||||
|
|
||||||
const e = shallow(
|
|
||||||
<RepositorySelect
|
|
||||||
assets={assets}
|
|
||||||
onChange={onChange}
|
|
||||||
localStore={testLocalStore()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
await waitForUpdate(e);
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(0);
|
|
||||||
expect(console.error).toHaveBeenCalledTimes(1);
|
|
||||||
// $ExpectFlowError
|
|
||||||
console.error = jest.fn();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("child onChange triggers parent onChange", () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const repoId = makeRepoId("foo", "bar");
|
|
||||||
mockRegistry([repoId]);
|
|
||||||
const e = mount(
|
|
||||||
<RepositorySelect
|
|
||||||
assets={assets}
|
|
||||||
onChange={onChange}
|
|
||||||
localStore={testLocalStore()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const child = e.find(PureRepositorySelect);
|
|
||||||
child.props().onChange(repoId);
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(1);
|
|
||||||
expect(onChange).toHaveBeenCalledWith(repoId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("selecting child option updates top-level state", async () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const repoIds = [makeRepoId("foo", "bar"), makeRepoId("z", "a")];
|
|
||||||
mockRegistry(repoIds);
|
|
||||||
const e = mount(
|
|
||||||
<RepositorySelect
|
|
||||||
assets={assets}
|
|
||||||
onChange={onChange}
|
|
||||||
localStore={testLocalStore()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
await waitForUpdate(e);
|
|
||||||
const child = e.find(PureRepositorySelect);
|
|
||||||
child.props().onChange(repoIds[0]);
|
|
||||||
const status: Status = e.state().status;
|
|
||||||
expect(status.type).toEqual("VALID");
|
|
||||||
if (status.type !== "VALID") {
|
|
||||||
throw new Error("Impossible");
|
|
||||||
}
|
|
||||||
expect(status.selectedRepoId).toEqual(repoIds[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -26,14 +26,10 @@ import {weightsToEdgeEvaluator} from "../analysis/weightsToEdgeEvaluator";
|
|||||||
|
|
||||||
export type LoadingState = "NOT_LOADING" | "LOADING" | "FAILED";
|
export type LoadingState = "NOT_LOADING" | "LOADING" | "FAILED";
|
||||||
export type AppState =
|
export type AppState =
|
||||||
| Uninitialized
|
|
||||||
| ReadyToLoadGraph
|
| ReadyToLoadGraph
|
||||||
| ReadyToRunPagerank
|
| ReadyToRunPagerank
|
||||||
| PagerankEvaluated;
|
| PagerankEvaluated;
|
||||||
|
|
||||||
export type Uninitialized = {|
|
|
||||||
+type: "UNINITIALIZED",
|
|
||||||
|};
|
|
||||||
export type ReadyToLoadGraph = {|
|
export type ReadyToLoadGraph = {|
|
||||||
+type: "READY_TO_LOAD_GRAPH",
|
+type: "READY_TO_LOAD_GRAPH",
|
||||||
+repoId: RepoId,
|
+repoId: RepoId,
|
||||||
@ -53,6 +49,10 @@ export type PagerankEvaluated = {|
|
|||||||
+loading: LoadingState,
|
+loading: LoadingState,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
export function initialState(repoId: RepoId): ReadyToLoadGraph {
|
||||||
|
return {type: "READY_TO_LOAD_GRAPH", repoId, loading: "NOT_LOADING"};
|
||||||
|
}
|
||||||
|
|
||||||
export function createStateTransitionMachine(
|
export function createStateTransitionMachine(
|
||||||
getState: () => AppState,
|
getState: () => AppState,
|
||||||
setState: (AppState) => void
|
setState: (AppState) => void
|
||||||
@ -65,13 +65,8 @@ export function createStateTransitionMachine(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uninitializedState(): AppState {
|
|
||||||
return {type: "UNINITIALIZED"};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exported for testing purposes.
|
// Exported for testing purposes.
|
||||||
export interface StateTransitionMachineInterface {
|
export interface StateTransitionMachineInterface {
|
||||||
+setRepoId: (RepoId) => void;
|
|
||||||
+loadGraph: (Assets, StaticAdapterSet) => Promise<boolean>;
|
+loadGraph: (Assets, StaticAdapterSet) => Promise<boolean>;
|
||||||
+runPagerank: (WeightedTypes, NodeAddressT) => Promise<void>;
|
+runPagerank: (WeightedTypes, NodeAddressT) => Promise<void>;
|
||||||
+loadGraphAndRunPagerank: (
|
+loadGraphAndRunPagerank: (
|
||||||
@ -119,15 +114,6 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||||||
this.pagerank = pagerank;
|
this.pagerank = pagerank;
|
||||||
}
|
}
|
||||||
|
|
||||||
setRepoId(repoId: RepoId) {
|
|
||||||
const newState: AppState = {
|
|
||||||
type: "READY_TO_LOAD_GRAPH",
|
|
||||||
repoId: repoId,
|
|
||||||
loading: "NOT_LOADING",
|
|
||||||
};
|
|
||||||
this.setState(newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Loads the graph, reports whether it was successful */
|
/** Loads the graph, reports whether it was successful */
|
||||||
async loadGraph(
|
async loadGraph(
|
||||||
assets: Assets,
|
assets: Assets,
|
||||||
@ -222,9 +208,6 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||||||
) {
|
) {
|
||||||
const state = this.getState();
|
const state = this.getState();
|
||||||
const type = state.type;
|
const type = state.type;
|
||||||
if (type === "UNINITIALIZED") {
|
|
||||||
throw new Error("Tried to load and run from incorrect state");
|
|
||||||
}
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "READY_TO_LOAD_GRAPH":
|
case "READY_TO_LOAD_GRAPH":
|
||||||
const loadedGraph = await this.loadGraph(assets, adapters);
|
const loadedGraph = await this.loadGraph(assets, adapters);
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
StateTransitionMachine,
|
StateTransitionMachine,
|
||||||
uninitializedState,
|
|
||||||
type AppState,
|
type AppState,
|
||||||
type GraphWithAdapters,
|
type GraphWithAdapters,
|
||||||
} from "./state";
|
} from "./state";
|
||||||
@ -82,62 +81,12 @@ describe("explorer/state", () => {
|
|||||||
return new Map();
|
return new Map();
|
||||||
}
|
}
|
||||||
function loading(state: AppState) {
|
function loading(state: AppState) {
|
||||||
if (state.type === "UNINITIALIZED") {
|
|
||||||
throw new Error("Tried to get invalid loading");
|
|
||||||
}
|
|
||||||
return state.loading;
|
return state.loading;
|
||||||
}
|
}
|
||||||
function getRepoId(state: AppState) {
|
|
||||||
if (state.type === "UNINITIALIZED") {
|
|
||||||
throw new Error("Tried to get invalid repoId");
|
|
||||||
}
|
|
||||||
return state.repoId;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("setRepoId", () => {
|
|
||||||
describe("in UNINITIALIZED", () => {
|
|
||||||
it("transitions to READY_TO_LOAD_GRAPH", () => {
|
|
||||||
const {getState, stm} = example(uninitializedState());
|
|
||||||
const repoId = makeRepoId("foo", "bar");
|
|
||||||
stm.setRepoId(repoId);
|
|
||||||
const state = getState();
|
|
||||||
expect(state.type).toBe("READY_TO_LOAD_GRAPH");
|
|
||||||
expect(getRepoId(state)).toEqual(repoId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("stays in READY_TO_LOAD_GRAPH with new repoId", () => {
|
|
||||||
const {getState, stm} = example(readyToLoadGraph());
|
|
||||||
const repoId = makeRepoId("zoink", "zod");
|
|
||||||
stm.setRepoId(repoId);
|
|
||||||
const state = getState();
|
|
||||||
expect(state.type).toBe("READY_TO_LOAD_GRAPH");
|
|
||||||
expect(getRepoId(state)).toEqual(repoId);
|
|
||||||
});
|
|
||||||
it("transitions READY_TO_RUN_PAGERANK to READY_TO_LOAD_GRAPH with new repoId", () => {
|
|
||||||
const {getState, stm} = example(readyToRunPagerank());
|
|
||||||
const repoId = makeRepoId("zoink", "zod");
|
|
||||||
stm.setRepoId(repoId);
|
|
||||||
const state = getState();
|
|
||||||
expect(state.type).toBe("READY_TO_LOAD_GRAPH");
|
|
||||||
expect(getRepoId(state)).toEqual(repoId);
|
|
||||||
});
|
|
||||||
it("transitions PAGERANK_EVALUATED to READY_TO_LOAD_GRAPH with new repoId", () => {
|
|
||||||
const {getState, stm} = example(pagerankEvaluated());
|
|
||||||
const repoId = makeRepoId("zoink", "zod");
|
|
||||||
stm.setRepoId(repoId);
|
|
||||||
const state = getState();
|
|
||||||
expect(state.type).toBe("READY_TO_LOAD_GRAPH");
|
|
||||||
expect(getRepoId(state)).toEqual(repoId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("loadGraph", () => {
|
describe("loadGraph", () => {
|
||||||
it("can only be called when READY_TO_LOAD_GRAPH", async () => {
|
it("can only be called when READY_TO_LOAD_GRAPH", async () => {
|
||||||
const badStates = [
|
const badStates = [readyToRunPagerank(), pagerankEvaluated()];
|
||||||
uninitializedState(),
|
|
||||||
readyToRunPagerank(),
|
|
||||||
pagerankEvaluated(),
|
|
||||||
];
|
|
||||||
for (const b of badStates) {
|
for (const b of badStates) {
|
||||||
const {stm} = example(b);
|
const {stm} = example(b);
|
||||||
await expect(
|
await expect(
|
||||||
@ -182,26 +131,6 @@ describe("explorer/state", () => {
|
|||||||
}
|
}
|
||||||
expect(state.graphWithAdapters).toBe(gwa);
|
expect(state.graphWithAdapters).toBe(gwa);
|
||||||
});
|
});
|
||||||
it("does not transition if repoId transition happens first", async () => {
|
|
||||||
const {getState, stm, loadGraphMock} = example(readyToLoadGraph());
|
|
||||||
const swappedRepoId = makeRepoId("too", "fast");
|
|
||||||
loadGraphMock.mockImplementation(
|
|
||||||
() =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
stm.setRepoId(swappedRepoId);
|
|
||||||
resolve(graphWithAdapters());
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const succeeded = await stm.loadGraph(
|
|
||||||
new Assets("/my/gateway/"),
|
|
||||||
new StaticAdapterSet([])
|
|
||||||
);
|
|
||||||
expect(succeeded).toBe(false);
|
|
||||||
const state = getState();
|
|
||||||
expect(loading(state)).toBe("NOT_LOADING");
|
|
||||||
expect(state.type).toBe("READY_TO_LOAD_GRAPH");
|
|
||||||
expect(getRepoId(state)).toEqual(swappedRepoId);
|
|
||||||
});
|
|
||||||
it("sets loading state FAILED on reject", async () => {
|
it("sets loading state FAILED on reject", async () => {
|
||||||
const {getState, stm, loadGraphMock} = example(readyToLoadGraph());
|
const {getState, stm, loadGraphMock} = example(readyToLoadGraph());
|
||||||
const error = new Error("Oh no!");
|
const error = new Error("Oh no!");
|
||||||
@ -223,13 +152,11 @@ describe("explorer/state", () => {
|
|||||||
|
|
||||||
describe("runPagerank", () => {
|
describe("runPagerank", () => {
|
||||||
it("can only be called when READY_TO_RUN_PAGERANK or PAGERANK_EVALUATED", async () => {
|
it("can only be called when READY_TO_RUN_PAGERANK or PAGERANK_EVALUATED", async () => {
|
||||||
const badStates = [uninitializedState(), readyToLoadGraph()];
|
const badState = readyToLoadGraph();
|
||||||
for (const b of badStates) {
|
const {stm} = example(badState);
|
||||||
const {stm} = example(b);
|
await expect(
|
||||||
await expect(
|
stm.runPagerank(weightedTypes(), NodeAddress.empty)
|
||||||
stm.runPagerank(weightedTypes(), NodeAddress.empty)
|
).rejects.toThrow("incorrect state");
|
||||||
).rejects.toThrow("incorrect state");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
it("can be run when READY_TO_RUN_PAGERANK or PAGERANK_EVALUATED", async () => {
|
it("can be run when READY_TO_RUN_PAGERANK or PAGERANK_EVALUATED", async () => {
|
||||||
const goodStates = [readyToRunPagerank(), pagerankEvaluated()];
|
const goodStates = [readyToRunPagerank(), pagerankEvaluated()];
|
||||||
@ -259,22 +186,6 @@ describe("explorer/state", () => {
|
|||||||
const args = pagerankMock.mock.calls[0];
|
const args = pagerankMock.mock.calls[0];
|
||||||
expect(args[2].totalScoreNodePrefix).toBe(foo);
|
expect(args[2].totalScoreNodePrefix).toBe(foo);
|
||||||
});
|
});
|
||||||
it("does not transition if a repoId change happens first", async () => {
|
|
||||||
const {getState, stm, pagerankMock} = example(readyToRunPagerank());
|
|
||||||
const swappedRepoId = makeRepoId("too", "fast");
|
|
||||||
pagerankMock.mockImplementation(
|
|
||||||
() =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
stm.setRepoId(swappedRepoId);
|
|
||||||
resolve(graphWithAdapters());
|
|
||||||
})
|
|
||||||
);
|
|
||||||
await stm.runPagerank(weightedTypes(), NodeAddress.empty);
|
|
||||||
const state = getState();
|
|
||||||
expect(loading(state)).toBe("NOT_LOADING");
|
|
||||||
expect(state.type).toBe("READY_TO_LOAD_GRAPH");
|
|
||||||
expect(getRepoId(state)).toBe(swappedRepoId);
|
|
||||||
});
|
|
||||||
it("sets loading state FAILED on reject", async () => {
|
it("sets loading state FAILED on reject", async () => {
|
||||||
const {getState, stm, pagerankMock} = example(readyToRunPagerank());
|
const {getState, stm, pagerankMock} = example(readyToRunPagerank());
|
||||||
const error = new Error("Oh no!");
|
const error = new Error("Oh no!");
|
||||||
@ -291,17 +202,6 @@ describe("explorer/state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("loadGraphAndRunPagerank", () => {
|
describe("loadGraphAndRunPagerank", () => {
|
||||||
it("errors if called with uninitialized state", async () => {
|
|
||||||
const {stm} = example(uninitializedState());
|
|
||||||
await expect(
|
|
||||||
stm.loadGraphAndRunPagerank(
|
|
||||||
new Assets("gateway"),
|
|
||||||
new StaticAdapterSet([]),
|
|
||||||
weightedTypes(),
|
|
||||||
NodeAddress.empty
|
|
||||||
)
|
|
||||||
).rejects.toThrow("incorrect state");
|
|
||||||
});
|
|
||||||
it("when READY_TO_LOAD_GRAPH, loads graph then runs pagerank", async () => {
|
it("when READY_TO_LOAD_GRAPH, loads graph then runs pagerank", async () => {
|
||||||
const {stm} = example(readyToLoadGraph());
|
const {stm} = example(readyToLoadGraph());
|
||||||
(stm: any).loadGraph = jest.fn();
|
(stm: any).loadGraph = jest.fn();
|
||||||
|
@ -4,28 +4,14 @@ import React, {type ComponentType} from "react";
|
|||||||
|
|
||||||
import type {RepoId} from "../core/repoId";
|
import type {RepoId} from "../core/repoId";
|
||||||
import type {Assets} from "../webutil/assets";
|
import type {Assets} from "../webutil/assets";
|
||||||
|
import HomepageExplorer from "./homepageExplorer";
|
||||||
|
|
||||||
export default function makeProjectPage(
|
export default function makeProjectPage(
|
||||||
repoId: RepoId
|
repoId: RepoId
|
||||||
): ComponentType<{|+assets: Assets|}> {
|
): ComponentType<{|+assets: Assets|}> {
|
||||||
return class ProjectPage extends React.Component<{|+assets: Assets|}> {
|
return class ProjectPage extends React.Component<{|+assets: Assets|}> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return <HomepageExplorer assets={this.props.assets} repoId={repoId} />;
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
maxWidth: 900,
|
|
||||||
margin: "0 auto",
|
|
||||||
marginBottom: 200,
|
|
||||||
padding: "0 10px",
|
|
||||||
lineHeight: 1.5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<strong>TODO:</strong> Render an explorer for{" "}
|
|
||||||
{`${repoId.owner}/${repoId.name}`}
|
|
||||||
</p>.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import {StaticAppAdapter as GithubAdapter} from "../plugins/github/appAdapter";
|
|||||||
import {StaticAppAdapter as GitAdapter} from "../plugins/git/appAdapter";
|
import {StaticAppAdapter as GitAdapter} from "../plugins/git/appAdapter";
|
||||||
import {GithubGitGateway} from "../plugins/github/githubGitGateway";
|
import {GithubGitGateway} from "../plugins/github/githubGitGateway";
|
||||||
import {AppPage} from "../explorer/App";
|
import {AppPage} from "../explorer/App";
|
||||||
|
import type {RepoId} from "../core/repoId";
|
||||||
|
|
||||||
function homepageStaticAdapters(): StaticAdapterSet {
|
function homepageStaticAdapters(): StaticAdapterSet {
|
||||||
return new StaticAdapterSet([
|
return new StaticAdapterSet([
|
||||||
@ -18,10 +19,15 @@ function homepageStaticAdapters(): StaticAdapterSet {
|
|||||||
|
|
||||||
export default class HomepageExplorer extends React.Component<{|
|
export default class HomepageExplorer extends React.Component<{|
|
||||||
+assets: Assets,
|
+assets: Assets,
|
||||||
|
+repoId: RepoId,
|
||||||
|}> {
|
|}> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<AppPage assets={this.props.assets} adapters={homepageStaticAdapters()} />
|
<AppPage
|
||||||
|
assets={this.props.assets}
|
||||||
|
repoId={this.props.repoId}
|
||||||
|
adapters={homepageStaticAdapters()}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,13 +39,13 @@ function makeRouteData(registry /*: RepoIdRegistry */) /*: RouteData */ {
|
|||||||
navTitle: "Home",
|
navTitle: "Home",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/prototypes/",
|
path: "/prototype/",
|
||||||
contents: {
|
contents: {
|
||||||
type: "PAGE",
|
type: "PAGE",
|
||||||
component: () => require("./PrototypesPage").default(registry),
|
component: () => require("./PrototypesPage").default(registry),
|
||||||
},
|
},
|
||||||
title: "SourceCred prototypes",
|
title: "SourceCred prototype",
|
||||||
navTitle: null, // for now
|
navTitle: "Prototype",
|
||||||
},
|
},
|
||||||
...registry.map((repo) => ({
|
...registry.map((repo) => ({
|
||||||
path: `/prototypes/${repo.owner}/${repo.name}/`,
|
path: `/prototypes/${repo.owner}/${repo.name}/`,
|
||||||
@ -56,15 +56,6 @@ function makeRouteData(registry /*: RepoIdRegistry */) /*: RouteData */ {
|
|||||||
title: `${repo.owner}/${repo.name} • SourceCred`,
|
title: `${repo.owner}/${repo.name} • SourceCred`,
|
||||||
navTitle: null,
|
navTitle: null,
|
||||||
})),
|
})),
|
||||||
{
|
|
||||||
path: "/prototype/",
|
|
||||||
contents: {
|
|
||||||
type: "PAGE",
|
|
||||||
component: () => require("./homepageExplorer").default,
|
|
||||||
},
|
|
||||||
title: "SourceCred prototype",
|
|
||||||
navTitle: "Prototype",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/discord-invite/",
|
path: "/discord-invite/",
|
||||||
contents: {
|
contents: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user