Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b4b7827
Add StudioDetailsRow component for displaying studio details
vtushar06 Nov 7, 2025
13c795b
Add StudioDetailsPanel component for displaying detailed studio infor…
vtushar06 Nov 7, 2025
8cc6dc6
Replace DetailsPanel with StudioDetailsPanel in ChannelDetailsModal
vtushar06 Nov 7, 2025
bf39104
Add unit tests for StudioDetailsRow component
vtushar06 Nov 7, 2025
fcfa1db
Add tests for StudioDetailsPanel component
vtushar06 Nov 7, 2025
4d126f9
Refactor StudioChip and StudioDetailsRow styles; update ChannelDetail…
vtushar06 Nov 7, 2025
47896ea
Remove unnecessary comments from StudioChip component
vtushar06 Nov 7, 2025
5600ff2
Remove duplicate StudioChip files - using contributor's implementatio…
vtushar06 Nov 20, 2025
02dd1fa
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Nov 20, 2025
96b7ac1
Enhance tests for StudioDetailsPanel and StudioDetailsRow components;…
vtushar06 Nov 20, 2025
ff8e9e0
Refactor StudioDetailsPanel and StudioDetailsRow components; improve …
vtushar06 Nov 20, 2025
2534418
Remove unnecessary comments from StudioDetailsPanel tests
vtushar06 Nov 20, 2025
2533dba
Refactor StudioDetailsPanel and StudioDetailsRow components; improve …
vtushar06 Dec 3, 2025
2443393
Merge branch 'unstable' into fix/channel-details-in-channel
vtushar06 Dec 3, 2025
b5b35d7
Update license chip reference to use dynamic keys for improved unique…
vtushar06 Dec 3, 2025
6c28b87
Add data-testid attribute to details panel for improved testing
vtushar06 Dec 3, 2025
11ec9b6
readdunit tests for StudioChip component functionality
vtushar06 Jan 20, 2026
f4ed7e4
updated StudioDetailsPanel to improve thumbnail handling and responsi…
vtushar06 Jan 20, 2026
507e085
Enhance StudioDetailsRow responsiveness with useKResponsiveWindow
vtushar06 Jan 20, 2026
558d629
Remove isChannel prop from StudioDetailsPanel tests
vtushar06 Jan 20, 2026
8970839
fixed linting issues in studiodetailspanel and studiodetailsrow
vtushar06 Jan 20, 2026
e4b2d60
updated import path for StudioCopyToken in StudioDetailsPanel
vtushar06 Jan 20, 2026
eb2100e
Add mock translation
vtushar06 Jan 21, 2026
6a297c0
resolved JS errors
vtushar06 Jan 21, 2026
b631ff5
updated mock in StudioDetailsPanel tests
vtushar06 Jan 26, 2026
5cf1564
Remove _uid
MisRob Jan 27, 2026
687b3ea
[TODO REVERT] QA channel data
MisRob Jan 27, 2026
d7e3edd
Remove CSS grid in favor of KDS grids
MisRob Jan 27, 2026
cc7fe93
Simplify thumbnails
MisRob Jan 27, 2026
20fcecf
Merge branch 'unstable' into fix/channel-details-in-channel
MisRob Jan 27, 2026
e92e700
Use StudioCopyToken
MisRob Jan 27, 2026
3866c4c
Improve chip styling
MisRob Jan 27, 2026
19c3049
Use KExternalLink
MisRob Jan 27, 2026
9c9e5c8
Semantic heading
MisRob Jan 27, 2026
4abc8e5
Remove obsolete class
MisRob Jan 27, 2026
e0712cc
Use StudioDetailsPanel for printing and in admin
MisRob Jan 27, 2026
8752764
Add printing support to StudioThumbnail
MisRob Jan 27, 2026
f50079e
Revert "[TODO REVERT] QA channel data"
MisRob Jan 27, 2026
cc82c6f
Improve loader position
MisRob Jan 27, 2026
dc2fc89
Simplify tests
MisRob Jan 27, 2026
b7938bb
Revert comment removal
MisRob Jan 27, 2026
d2621fb
Revert print style selector
MisRob Jan 27, 2026
21ec59b
Return test id
MisRob Jan 27, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
flat
class="px-5"
>
<DetailsPanel
<StudioDetailsPanel
v-if="channel && details"
:details="channelWithDetails"
:loading="loading"
Expand Down Expand Up @@ -87,7 +87,7 @@
import { RouteNames } from '../../constants';
import ChannelActionsDropdown from './ChannelActionsDropdown';
import ChannelSharing from 'shared/views/channel/ChannelSharing';
import DetailsPanel from 'shared/views/details/DetailsPanel';
import StudioDetailsPanel from 'shared/views/details/StudioDetailsPanel';
import { routerMixin } from 'shared/mixins';
import LoadingText from 'shared/views/LoadingText';
import FullscreenModal from 'shared/views/FullscreenModal';
Expand All @@ -96,7 +96,7 @@
export default {
name: 'ChannelDetails',
components: {
DetailsPanel,
StudioDetailsPanel,
LoadingText,
FullscreenModal,
ChannelSharing,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@
<script>

import { mapActions, mapGetters } from 'vuex';
import StudioChip from 'shared/views/StudioChip';
import ExpandableList from 'shared/views/ExpandableList';
import { generateFormMixin } from 'shared/mixins';
import StudioChip from 'shared/views/StudioChip';

const formMixin = generateFormMixin({
subject: { required: true },
Expand Down
22 changes: 22 additions & 0 deletions contentcuration/contentcuration/frontend/shared/utils/icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ContentKindsNames } from 'shared/leUtils/ContentKinds';

const EMPTY = '_empty';
const CONTENT_KIND_ICONS = {
[ContentKindsNames.TOPIC]: 'topic',
[ContentKindsNames.TOPIC + EMPTY]: 'emptyTopic',
[ContentKindsNames.VIDEO]: 'video',
[ContentKindsNames.AUDIO]: 'audio',
[ContentKindsNames.SLIDESHOW]: 'slideshow',
[ContentKindsNames.EXERCISE]: 'exercise',
[ContentKindsNames.DOCUMENT]: 'document',
[ContentKindsNames.HTML5]: 'html5',
[ContentKindsNames.ZIM]: 'html5',
};

export function getContentKindIcon(kind, isEmpty = false) {
const icon = (isEmpty ? [kind + EMPTY] : []).concat([kind]).find(k => k in CONTENT_KIND_ICONS);
if (!icon) {
throw new Error(`Icon not found for content kind: ${kind}`);
}
return CONTENT_KIND_ICONS[icon];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { render, screen } from '@testing-library/vue';
import VueRouter from 'vue-router';
import ContentLevels from 'kolibri-constants/labels/Levels';
import Categories from 'kolibri-constants/labels/Subjects';
import StudioDetailsPanel from '../details/StudioDetailsPanel.vue';

const renderComponent = (props = {}) => {
return render(StudioDetailsPanel, {
props,
routes: new VueRouter(),
});
};

describe('StudioDetailsPanel', () => {
const fullChannel = {
name: 'Complete Channel',
description: 'A fully populated channel',
thumbnail_url: 'https://example.com/thumb.jpg',
published: true,
version: 2,
primary_token: 'abc12345',
language: 'en',
created: '2025-01-15T10:00:00Z',
last_published: '2025-01-20T15:30:00Z',
resource_count: 42,
resource_size: 1024000000,
kind_count: [],
levels: [ContentLevels.LOWER_PRIMARY, ContentLevels.UPPER_PRIMARY],
categories: [Categories.MATHEMATICS, Categories.SCIENCES],
includes: { coach_content: 1, exercises: 1 },
tags: [{ tag_name: 'science' }, { tag_name: 'math' }],
languages: [],
accessible_languages: [],
authors: ['Author One', 'Author Two'],
providers: [],
aggregators: [],
licenses: [],
copyright_holders: [],
original_channels: [],
sample_nodes: [],
};

const minimalChannel = {
name: 'Minimal Channel',
description: '',
thumbnail_url: null,
published: false,
version: null,
primary_token: null,
language: null,
created: null,
last_published: null,
resource_count: 0,
resource_size: 0,
kind_count: [],
levels: [],
categories: [],
includes: { coach_content: 0, exercises: 0 },
tags: [],
languages: [],
accessible_languages: [],
authors: [],
providers: [],
aggregators: [],
licenses: [],
copyright_holders: [],
original_channels: [],
sample_nodes: [],
};

it('renders channel header with name and description', () => {
renderComponent({ details: fullChannel, loading: false });

expect(screen.getByText('Complete Channel')).toBeInTheDocument();
expect(screen.getByText('A fully populated channel')).toBeInTheDocument();
});

describe('published channel with full data', () => {
beforeEach(() => {
renderComponent({ details: fullChannel, loading: false });
});

it('displays published status and version', () => {
expect(screen.getByText('Published on')).toBeInTheDocument();
expect(screen.getByText('Published version')).toBeInTheDocument();
});

it('displays translated levels and categories', () => {
expect(screen.getByText('Lower primary')).toBeInTheDocument();
expect(screen.getByText('Upper primary')).toBeInTheDocument();
expect(screen.getByText('Mathematics')).toBeInTheDocument();
expect(screen.getByText('Sciences')).toBeInTheDocument();
});

it('displays resource count and metadata', () => {
expect(screen.getByText('42')).toBeInTheDocument();
expect(screen.getByText('Author One')).toBeInTheDocument();
expect(screen.getByText('Author Two')).toBeInTheDocument();
expect(screen.getByText('science')).toBeInTheDocument();
expect(screen.getByText('math')).toBeInTheDocument();
});
});

describe('unpublished channel with missing data', () => {
beforeEach(() => {
renderComponent({ details: minimalChannel, loading: false });
});

it('displays unpublished status', () => {
expect(screen.getByText('Unpublished')).toBeInTheDocument();
});

it('shows placeholder text for empty fields', () => {
const placeholders = screen.getAllByText('---');
expect(placeholders.length).toBeGreaterThan(0);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { render, screen } from '@testing-library/vue';
import VueRouter from 'vue-router';
import StudioDetailsRow from '../details/StudioDetailsRow.vue';

const renderComponent = (props = {}, slots = {}) => {
return render(StudioDetailsRow, {
props,
slots,
routes: new VueRouter(),
});
};

describe('StudioDetailsRow', () => {
it('renders label and text value', () => {
renderComponent({
label: 'Channel size',
text: '1.5 GB',
});

expect(screen.getByText('Channel size')).toBeInTheDocument();
expect(screen.getByText('1.5 GB')).toBeInTheDocument();
});

it('renders slot content', () => {
renderComponent({ label: 'Authors' }, { default: '<div>Author One, Author Two</div>' });

expect(screen.getByText('Authors')).toBeInTheDocument();
expect(screen.getByText('Author One, Author Two')).toBeInTheDocument();
});

it('displays tooltip when definition is provided', () => {
renderComponent({
label: 'Resources for coaches',
text: '5',
definition: 'Resources only visible to coaches',
});

expect(screen.getByLabelText('Resources only visible to coaches')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
:channelList="channelList"
:style="pageStyle"
/>
<DetailsPanel
<StudioDetailsPanel
v-for="channelWithDetails in channelList"
:key="channelWithDetails.id"
ref="details"
Expand All @@ -25,15 +25,15 @@

<script>

import DetailsPanel from '../details/DetailsPanel.vue';
import StudioDetailsPanel from '../details/StudioDetailsPanel';
import { fitToScale, generatePdf } from '../../utils/helpers';
import ChannelCatalogFrontPage from './ChannelCatalogFrontPage';

export default {
name: 'ChannelCatalogPrint',
components: {
ChannelCatalogFrontPage,
DetailsPanel,
StudioDetailsPanel,
},
provide: {
printing: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
<template #header>
<span class="notranslate">{{ channel ? channel.name : '' }}</span>
</template>
<StudioLargeLoader v-if="show('channelDetails', loading, 500)" />
<StudioLargeLoader
v-if="show('channelDetails', loading, 500)"
:style="{ marginTop: '160px' }"
/>
<div v-else-if="channel">
<div
class="download-button-container"
Expand All @@ -24,7 +27,7 @@
</template>
</KButton>
</div>
<DetailsPanel
<StudioDetailsPanel
v-if="channel && details"
class="channel-details-wrapper"
:details="channelWithDetails"
Expand All @@ -43,15 +46,15 @@
import useKShow from 'kolibri-design-system/lib/composables/useKShow';
import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
import { channelExportMixin } from './mixins';
import DetailsPanel from 'shared/views/details/DetailsPanel.vue';
import StudioDetailsPanel from 'shared/views/details/StudioDetailsPanel.vue';
import StudioLargeLoader from 'shared/views/StudioLargeLoader';
import StudioImmersiveModal from 'shared/views/StudioImmersiveModal';
import { routerMixin } from 'shared/mixins';

export default {
name: 'ChannelDetailsModal',
components: {
DetailsPanel,
StudioDetailsPanel,
StudioLargeLoader,
StudioImmersiveModal,
},
Expand Down
Loading