Skip to content
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
5 changes: 4 additions & 1 deletion app/Http/Controllers/BuildController.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ public function update(int $build_id): View
{
$this->setBuildById($build_id);

return $this->vue('build-update', 'Files Updated');
return $this->vue('build-update', 'Files Updated', [
'repository-type' => $this->project->CvsViewerType,
'repository-url' => $this->project->CvsUrl,
]);
}

public function tests(int $build_id): View
Expand Down
21 changes: 19 additions & 2 deletions resources/js/vue/components/BuildUpdate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<tt v-if="cdash.update.revisionurl.length > 0">
<a
class="tw-link tw-link-hover tw-link-info"
:href="cdash.update.revisionurl"
:href="repository?.getComparisonUrl(cdash.update.revision, cdash.update.priorrevision) ?? ''"
>{{ cdash.update.revision }}</a>
</tt>
<tt v-else>
Expand All @@ -27,7 +27,7 @@
<tt v-if="cdash.update.revisiondiff.length > 0">
<a
class="tw-link tw-link-hover tw-link-info"
:href="cdash.update.revisiondiff"
:href="repository?.getCommitUrl(cdash.update.priorrevision) ?? ''"
>{{ cdash.update.priorrevision }}</a>
</tt>
<tt v-else>
Expand Down Expand Up @@ -131,11 +131,24 @@ import LoadingIndicator from './shared/LoadingIndicator.vue';
import CodeBox from './shared/CodeBox.vue';
import {faChevronDown, faChevronRight} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
import {getRepository} from './shared/RepositoryIntegrations';

export default {
name: 'BuildUpdate',
components: {FontAwesomeIcon, CodeBox, LoadingIndicator, BuildSummaryCard},

props: {
repositoryType: {
type: String,
required: true,
},

repositoryUrl: {
type: String,
required: true,
},
},

data() {
return {
// API results.
Expand Down Expand Up @@ -167,6 +180,10 @@ export default {
faChevronRight,
};
},

repository() {
return getRepository(this.repositoryType, this.repositoryUrl);
},
},

async mounted() {
Expand Down
83 changes: 83 additions & 0 deletions resources/js/vue/components/shared/RepositoryIntegrations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Combine URL components, ensuring that trailing slashes are stripped before concatenation.
*
* @param {...String} components
*/
function makeUrlFromComponents(...components) {
return components.map(part => part.replace(/\/+$/, '')).join('/');
}

export class Repository {
constructor(repositoryUrl) {
this.repositoryUrl = repositoryUrl;
}

/**
* @param {String} commit
* @return String
*/
getCommitUrl(commit) { // eslint-disable-line no-unused-vars
throw new Error('Method not implemented for abstract Repository class.');
}

/**
* @param {String} commit1
* @param {String} commit2
* @return String
*/
getComparisonUrl(commit1, commit2) { // eslint-disable-line no-unused-vars
throw new Error('Method not implemented for abstract Repository class.');
}

/**
* @param {String} commit
* @param {String} path
* @return String
*/
getFileUrl(commit, path) { // eslint-disable-line no-unused-vars
throw new Error('Method not implemented for abstract Repository class.');
}
}

export class GitHub extends Repository {
getCommitUrl(commit) {
return makeUrlFromComponents(this.repositoryUrl, 'commit', commit);
}

getComparisonUrl(commit1, commit2) {
return makeUrlFromComponents(this.repositoryUrl, 'compare', `${commit1}...${commit2}`);
}

getFileUrl(commit, path) {
return makeUrlFromComponents(this.repositoryUrl, 'blob', commit, path);
}
}

export class GitLab extends Repository {
getCommitUrl(commit) {
return makeUrlFromComponents(this.repositoryUrl, '-', 'commit', commit);
}

getComparisonUrl(commit1, commit2) {
return makeUrlFromComponents(this.repositoryUrl, '-', 'compare', `${commit1}...${commit2}`);
}
getFileUrl(commit, path) {
return makeUrlFromComponents(this.repositoryUrl, '-', 'blob', commit, path);
}
}

/**
* @param {String} repositoryType
* @param {String} repositoryUrl
* @return ?Repository
*/
export function getRepository(repositoryType, repositoryUrl) {
switch (repositoryType.toLowerCase()) {
case 'github':
return new GitHub(repositoryUrl);
case 'gitlab':
return new GitLab(repositoryUrl);
default:
return null;
}
}
11 changes: 6 additions & 5 deletions tests/Spec/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
function(add_vue_test TestName)
function(add_jest_test TestName)
add_test(
NAME "Spec/${TestName}"
COMMAND "node_modules/.bin/jest" "tests/Spec/${TestName}.spec.js"
WORKING_DIRECTORY "${CDash_SOURCE_DIR}"
)
endfunction()

add_vue_test(build-summary)
add_vue_test(edit-project)
add_vue_test(manage-measurements)
add_vue_test(test-details)
add_jest_test(build-summary)
add_jest_test(edit-project)
add_jest_test(manage-measurements)
add_jest_test(test-details)
add_jest_test(repository-integrations)
104 changes: 104 additions & 0 deletions tests/Spec/repository-integrations.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
getRepository,
GitHub,
GitLab,
Repository,
} from '../../resources/js/vue/components/shared/RepositoryIntegrations';

describe('RepositoryIntegrations', () => {
describe('getRepository', () => {
it('returns a GitHub instance for "github" type', () => {
const repo = getRepository('github', 'https://github.com/foo/bar');
expect(repo).toBeInstanceOf(GitHub);
expect(repo.repositoryUrl).toBe('https://github.com/foo/bar');
});

it('returns a GitLab instance for "gitlab" type', () => {
const repo = getRepository('gitlab', 'https://gitlab.com/foo/bar');
expect(repo).toBeInstanceOf(GitLab);
expect(repo.repositoryUrl).toBe('https://gitlab.com/foo/bar');
});

it('is case insensitive for repository type', () => {
const repo = getRepository('GitHub', 'https://github.com/foo/bar');
expect(repo).toBeInstanceOf(GitHub);
});

it('returns null for unknown repository types', () => {
const repo = getRepository('bitbucket', 'https://bitbucket.org/foo/bar');
expect(repo).toBeNull();
});
});

describe('GitHub', () => {
const repoUrl = 'https://github.com/foo/bar';
const repo = new GitHub(repoUrl);

it('generates correct commit URL', () => {
const commit = 'abcdef123456';
expect(repo.getCommitUrl(commit)).toBe(`${repoUrl}/commit/${commit}`);
});

it('generates correct comparison URL', () => {
const commit1 = 'abc';
const commit2 = 'def';
expect(repo.getComparisonUrl(commit1, commit2)).toBe(`${repoUrl}/compare/${commit1}...${commit2}`);
});

it('generates correct file URL', () => {
const commit = 'abcdef123456';
const path = 'src/main.cpp';
expect(repo.getFileUrl(commit, path)).toBe(`${repoUrl}/blob/${commit}/${path}`);
});

it('handles trailing slashes in repository URL', () => {
const repoWithSlash = new GitHub('https://github.com/foo/bar/');
const commit = '123';
expect(repoWithSlash.getCommitUrl(commit)).toBe('https://github.com/foo/bar/commit/123');
});
});

describe('GitLab', () => {
const repoUrl = 'https://gitlab.com/foo/bar';
const repo = new GitLab(repoUrl);

it('generates correct commit URL', () => {
const commit = 'abcdef123456';
expect(repo.getCommitUrl(commit)).toBe(`${repoUrl}/-/commit/${commit}`);
});

it('generates correct comparison URL', () => {
const commit1 = 'abc';
const commit2 = 'def';
expect(repo.getComparisonUrl(commit1, commit2)).toBe(`${repoUrl}/-/compare/${commit1}...${commit2}`);
});

it('generates correct file URL', () => {
const commit = 'abcdef123456';
const path = 'src/main.cpp';
expect(repo.getFileUrl(commit, path)).toBe(`${repoUrl}/-/blob/${commit}/${path}`);
});

it('handles trailing slashes in repository URL', () => {
const repoWithSlash = new GitLab('https://gitlab.com/foo/bar/');
const commit = '123';
expect(repoWithSlash.getCommitUrl(commit)).toBe('https://gitlab.com/foo/bar/-/commit/123');
});
});

describe('Repository (Abstract)', () => {
const repo = new Repository('https://example.com');

it('throws error for unimplemented getCommitUrl', () => {
expect(() => repo.getCommitUrl('123')).toThrow('Method not implemented for abstract Repository class.');
});

it('throws error for unimplemented getComparisonUrl', () => {
expect(() => repo.getComparisonUrl('123', '456')).toThrow('Method not implemented for abstract Repository class.');
});

it('throws error for unimplemented getFileUrl', () => {
expect(() => repo.getFileUrl('123', 'file.txt')).toThrow('Method not implemented for abstract Repository class.');
});
});
});