mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
added defaultBranch to Branches type, changed ui-bundler version for better testing experience in intellij, corrected fetchBranch functionality, wrote reducer for single branch and unit tests
This commit is contained in:
@@ -4,5 +4,6 @@ import type {Links} from "./hal";
|
|||||||
export type Branch = {
|
export type Branch = {
|
||||||
name: string,
|
name: string,
|
||||||
revision: string,
|
revision: string,
|
||||||
|
defaultBranch?: boolean,
|
||||||
_links: Links
|
_links: Links
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
"pre-commit": "jest && flow && eslint src"
|
"pre-commit": "jest && flow && eslint src"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.26",
|
"@scm-manager/ui-bundler": "^0.0.27",
|
||||||
"concat": "^1.0.3",
|
"concat": "^1.0.3",
|
||||||
"copyfiles": "^2.0.0",
|
"copyfiles": "^2.0.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.3.0",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const styles = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class BranchRow extends React.Component<Props> {
|
class BranchRow extends React.Component<Props> {
|
||||||
renderLink(to: string, label: string, defaultBranch: boolean) {
|
renderLink(to: string, label: string, defaultBranch?: boolean) {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
|
|
||||||
let showLabel = null;
|
let showLabel = null;
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { connect } from "react-redux";
|
|||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { withRouter } from "react-router-dom";
|
import { withRouter } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
fetchBranchByName,
|
fetchBranch,
|
||||||
getBranchByName,
|
getBranch,
|
||||||
getFetchBranchFailure,
|
getFetchBranchFailure,
|
||||||
isFetchBranchPending
|
isFetchBranchPending
|
||||||
} from "../modules/branches";
|
} from "../modules/branches";
|
||||||
@@ -22,7 +22,7 @@ type Props = {
|
|||||||
branch: Branch,
|
branch: Branch,
|
||||||
|
|
||||||
// dispatch functions
|
// dispatch functions
|
||||||
fetchBranchByName: (repository: Repository, branchName: string) => void,
|
fetchBranch: (repository: Repository, branchName: string) => void,
|
||||||
|
|
||||||
// context props
|
// context props
|
||||||
t: string => string
|
t: string => string
|
||||||
@@ -30,9 +30,9 @@ type Props = {
|
|||||||
|
|
||||||
class BranchView extends React.Component<Props> {
|
class BranchView extends React.Component<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { fetchBranchByName, repository, branchName } = this.props;
|
const { fetchBranch, repository, branchName } = this.props;
|
||||||
|
|
||||||
fetchBranchByName(repository, branchName);
|
fetchBranch(repository, branchName);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -71,7 +71,7 @@ class BranchView extends React.Component<Props> {
|
|||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const { repository } = ownProps;
|
const { repository } = ownProps;
|
||||||
const branchName = decodeURIComponent(ownProps.match.params.branch);
|
const branchName = decodeURIComponent(ownProps.match.params.branch);
|
||||||
const branch = getBranchByName(state, branchName);
|
const branch = getBranch(state, repository, branchName);
|
||||||
const loading = isFetchBranchPending(state, branchName);
|
const loading = isFetchBranchPending(state, branchName);
|
||||||
const error = getFetchBranchFailure(state, branchName);
|
const error = getFetchBranchFailure(state, branchName);
|
||||||
return {
|
return {
|
||||||
@@ -85,8 +85,8 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
fetchBranchByName: (repository: Repository, branchName: string) => {
|
fetchBranch: (repository: Repository, branchName: string) => {
|
||||||
dispatch(fetchBranchByName(repository._links.branches.href, branchName));
|
dispatch(fetchBranch(repository, branchName));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import {
|
|||||||
SUCCESS_SUFFIX
|
SUCCESS_SUFFIX
|
||||||
} from "../../../modules/types";
|
} from "../../../modules/types";
|
||||||
import { apiClient } from "@scm-manager/ui-components";
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
import type {
|
import type { Action, Branch, Repository } from "@scm-manager/ui-types";
|
||||||
Action,
|
|
||||||
Branch,
|
|
||||||
Repository
|
|
||||||
} from "@scm-manager/ui-types";
|
|
||||||
import { isPending } from "../../../modules/pending";
|
import { isPending } from "../../../modules/pending";
|
||||||
import { getFailure } from "../../../modules/failure";
|
import { getFailure } from "../../../modules/failure";
|
||||||
|
|
||||||
@@ -25,61 +21,69 @@ export const FETCH_BRANCH_FAILURE = `${FETCH_BRANCH}_${FAILURE_SUFFIX}`;
|
|||||||
|
|
||||||
// Fetching branches
|
// Fetching branches
|
||||||
|
|
||||||
export function fetchBranchByName(link: string, name: string) {
|
function createIdentifier(repository: Repository) {
|
||||||
let endsWith = "";
|
return repository.namespace + "/" + repository.name;
|
||||||
if(!link.endsWith("/")) {
|
|
||||||
endsWith = "/";
|
|
||||||
}
|
|
||||||
return fetchBranch(link + endsWith + encodeURIComponent(name), name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchBranchPending(name: string): Action {
|
export function fetchBranchPending(
|
||||||
|
repository: Repository,
|
||||||
|
name: string
|
||||||
|
): Action {
|
||||||
return {
|
return {
|
||||||
type: FETCH_BRANCH_PENDING,
|
type: FETCH_BRANCH_PENDING,
|
||||||
payload: name,
|
payload: { repository, name },
|
||||||
itemId: name
|
itemId: createIdentifier(repository) + "/" + name
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchBranchSuccess(repo: Repository, branch: Branch): Action {
|
export function fetchBranchSuccess(
|
||||||
|
repository: Repository,
|
||||||
|
branch: Branch
|
||||||
|
): Action {
|
||||||
return {
|
return {
|
||||||
type: FETCH_BRANCH_SUCCESS,
|
type: FETCH_BRANCH_SUCCESS,
|
||||||
payload: branch,
|
payload: { repository, branch },
|
||||||
itemId: branch.name
|
itemId: createIdentifier(repository) + "/" + branch.name
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchBranchFailure(name: string, error: Error): Action {
|
export function fetchBranchFailure(
|
||||||
|
repository: Repository,
|
||||||
|
name: string,
|
||||||
|
error: Error
|
||||||
|
): Action {
|
||||||
return {
|
return {
|
||||||
type: FETCH_BRANCH_FAILURE,
|
type: FETCH_BRANCH_FAILURE,
|
||||||
payload: name,
|
payload: { error, repository, name },
|
||||||
itemId: name
|
itemId: createIdentifier(repository) + "/" + name
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchBranch(link: string, name: string) {
|
export function fetchBranch(
|
||||||
|
repository: Repository,
|
||||||
|
name: string
|
||||||
|
) {
|
||||||
|
let link = repository._links.branches.href;
|
||||||
|
if (!link.endsWith("/")) {
|
||||||
|
link += "/";
|
||||||
|
}
|
||||||
|
link += encodeURIComponent(name);
|
||||||
return function(dispatch: any) {
|
return function(dispatch: any) {
|
||||||
dispatch(fetchBranchPending(name));
|
dispatch(fetchBranchPending(repository, name));
|
||||||
return apiClient
|
return apiClient
|
||||||
.get(link)
|
.get(link)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
dispatch(fetchBranchSuccess(data));
|
dispatch(fetchBranchSuccess(repository, data));
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
dispatch(fetchBranchFailure(name, error));
|
dispatch(fetchBranchFailure(repository, name, error));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBranchByName(state: Object, name: string) {
|
|
||||||
if (state.branches) {
|
|
||||||
return state.branches[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFetchBranchPending(state: Object, name: string) {
|
export function isFetchBranchPending(state: Object, name: string) {
|
||||||
return isPending(state, FETCH_BRANCH, name);
|
return isPending(state, FETCH_BRANCH, name);
|
||||||
}
|
}
|
||||||
@@ -138,6 +142,24 @@ export function fetchBranchesFailure(repository: Repository, error: Error) {
|
|||||||
|
|
||||||
// Reducers
|
// Reducers
|
||||||
|
|
||||||
|
function reduceBranchSuccess(state, repositoryName, newBranch) {
|
||||||
|
const newBranches = [];
|
||||||
|
// we do not use filter, because we try to keep the current order
|
||||||
|
let found = false;
|
||||||
|
for (const branch of state[repositoryName] || []) {
|
||||||
|
if (branch.name === newBranch.name) {
|
||||||
|
newBranches.push(newBranch);
|
||||||
|
found = true;
|
||||||
|
} else {
|
||||||
|
newBranches.push(branch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
newBranches.push(newBranch);
|
||||||
|
}
|
||||||
|
return newBranches;
|
||||||
|
}
|
||||||
|
|
||||||
type State = { [string]: Branch[] };
|
type State = { [string]: Branch[] };
|
||||||
|
|
||||||
export default function reducer(
|
export default function reducer(
|
||||||
@@ -156,11 +178,15 @@ export default function reducer(
|
|||||||
[key]: extractBranchesFromPayload(payload.data)
|
[key]: extractBranchesFromPayload(payload.data)
|
||||||
};
|
};
|
||||||
case FETCH_BRANCH_SUCCESS:
|
case FETCH_BRANCH_SUCCESS:
|
||||||
|
if (!action.payload.repository || !action.payload.branch) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
const newBranch = action.payload.branch;
|
||||||
|
const repositoryName = createIdentifier(action.payload.repository);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
[action.payload.name]: action.payload
|
[repositoryName]: reduceBranchSuccess(state, repositoryName, newBranch)
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ import reducer, {
|
|||||||
FETCH_BRANCH_SUCCESS,
|
FETCH_BRANCH_SUCCESS,
|
||||||
FETCH_BRANCH_FAILURE,
|
FETCH_BRANCH_FAILURE,
|
||||||
fetchBranches,
|
fetchBranches,
|
||||||
fetchBranchByName,
|
|
||||||
fetchBranchSuccess,
|
|
||||||
fetchBranch,
|
fetchBranch,
|
||||||
|
fetchBranchSuccess,
|
||||||
getBranch,
|
getBranch,
|
||||||
getBranches,
|
getBranches,
|
||||||
getFetchBranchesFailure,
|
getFetchBranchesFailure,
|
||||||
@@ -100,7 +99,7 @@ describe("branches", () => {
|
|||||||
fetchMock.getOnce(URL + "/branch1", branch1);
|
fetchMock.getOnce(URL + "/branch1", branch1);
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store.dispatch(fetchBranchByName(URL, "branch1")).then(() => {
|
return store.dispatch(fetchBranch(repository, "branch1")).then(() => {
|
||||||
const actions = store.getActions();
|
const actions = store.getActions();
|
||||||
expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING);
|
expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING);
|
||||||
expect(actions[1].type).toEqual(FETCH_BRANCH_SUCCESS);
|
expect(actions[1].type).toEqual(FETCH_BRANCH_SUCCESS);
|
||||||
@@ -114,7 +113,7 @@ describe("branches", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store.dispatch(fetchBranchByName(URL, "branch2")).then(() => {
|
return store.dispatch(fetchBranch(repository, "branch2")).then(() => {
|
||||||
const actions = store.getActions();
|
const actions = store.getActions();
|
||||||
expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING);
|
expect(actions[0].type).toEqual(FETCH_BRANCH_PENDING);
|
||||||
expect(actions[1].type).toEqual(FETCH_BRANCH_FAILURE);
|
expect(actions[1].type).toEqual(FETCH_BRANCH_FAILURE);
|
||||||
@@ -149,51 +148,60 @@ describe("branches", () => {
|
|||||||
const oldState = {
|
const oldState = {
|
||||||
"hitchhiker/heartOfGold": [branch3]
|
"hitchhiker/heartOfGold": [branch3]
|
||||||
};
|
};
|
||||||
|
|
||||||
const newState = reducer(oldState, action);
|
const newState = reducer(oldState, action);
|
||||||
expect(newState[key]).toContain(branch1);
|
expect(newState[key]).toContain(branch1);
|
||||||
expect(newState[key]).toContain(branch2);
|
expect(newState[key]).toContain(branch2);
|
||||||
|
|
||||||
expect(newState["hitchhiker/heartOfGold"]).toContain(branch3);
|
expect(newState["hitchhiker/heartOfGold"]).toContain(branch3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update state according to FETCH_BRANCH_SUCCESS action", () => {
|
it("should update state according to FETCH_BRANCH_SUCCESS action", () => {
|
||||||
const newState = reducer({}, fetchBranchSuccess(branch3));
|
const newState = reducer({}, fetchBranchSuccess(repository, branch3));
|
||||||
expect(newState["branch3"]).toBe(branch3);
|
expect(newState["foo/bar"]).toEqual([branch3]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not delete existing branch from state", () => {
|
it("should not delete existing branch from state", () => {
|
||||||
const oldState = {
|
const oldState = {
|
||||||
branch1
|
"foo/bar": [branch1]
|
||||||
};
|
};
|
||||||
|
const newState = reducer(oldState, fetchBranchSuccess(repository, branch2));
|
||||||
const newState = reducer(oldState, fetchBranchSuccess(branch2));
|
expect(newState["foo/bar"]).toEqual([branch1, branch2]);
|
||||||
expect(newState["branch1"]).toBe(branch1);
|
|
||||||
expect(newState["branch2"]).toBe(branch2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update required branch from state", () => {
|
it("should update required branch from state", () => {
|
||||||
const oldState = {
|
const oldState = {
|
||||||
branch1
|
"foo/bar": [branch1]
|
||||||
};
|
};
|
||||||
|
|
||||||
const newBranch1 = { name: "branch1", revision: "revision2" };
|
const newBranch1 = { name: "branch1", revision: "revision2" };
|
||||||
|
const newState = reducer(oldState, fetchBranchSuccess(repository, newBranch1));
|
||||||
const newState = reducer(oldState, fetchBranchSuccess(newBranch1));
|
expect(newState["foo/bar"]).toEqual([newBranch1]);
|
||||||
expect(newState["branch1"]).not.toBe(branch1);
|
|
||||||
expect(newState["branch1"]).toBe(newBranch1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update required branch from state and keeps old repo", () => {
|
it("should update required branch from state and keeps old repo", () => {
|
||||||
const oldState = {
|
const oldState = {
|
||||||
repo1: {
|
"ns/one": [branch1]
|
||||||
branch1
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const repo2 = { repo2: { branch3 } };
|
const newState = reducer(oldState, fetchBranchSuccess(repository, branch3));
|
||||||
const newState = reducer(oldState, fetchBranchSuccess(repo2, branch2));
|
expect(newState["ns/one"]).toEqual([branch1]);
|
||||||
expect(newState["repo1"]).toBe({ branch1 });
|
expect(newState["foo/bar"]).toEqual([branch3]);
|
||||||
expect(newState["repo2"]).toBe({ branch2, branch3 });
|
});
|
||||||
|
|
||||||
|
it("should return the oldState, if action has no payload", () => {
|
||||||
|
const state = {};
|
||||||
|
const newState = reducer(state, {type: FETCH_BRANCH_SUCCESS});
|
||||||
|
expect(newState).toBe(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the oldState, if payload has no branch", () => {
|
||||||
|
const action = {
|
||||||
|
type: FETCH_BRANCH_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
repository
|
||||||
|
},
|
||||||
|
itemId: "foo/bar/"
|
||||||
|
};
|
||||||
|
const state = {};
|
||||||
|
const newState = reducer(state, action);
|
||||||
|
expect(newState).toBe(state);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -698,9 +698,10 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.26":
|
"@scm-manager/ui-bundler@^0.0.27":
|
||||||
version "0.0.26"
|
version "0.0.27"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.26.tgz#4676a7079b781b33fa1989c6643205c3559b1f66"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.27.tgz#3ed2c7826780b9a1a9ea90464332640cfb5d54b5"
|
||||||
|
integrity sha512-cBU1xq6gDy1Vw9AGOzsR763+JmBeraTaC/KQfxT3I6XyZJ2brIfG1m5QYcAcHWvDxq3mYMogpI5rfShw14L4/w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user