diff --git a/.circleci/config.yml b/.circleci/config.yml index 0aada78dd74..681cfa58b5b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,3 +54,4 @@ workflows: build_and_test: jobs: - test-cases + \ No newline at end of file diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 00000000000..9f46399d100 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,106 @@ +name: CI/CD + +on: + push: + branches: + - "*" # Trigger on push to any branch + pull_request: + branches: + - "*" # Trigger on pull request to any branch + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + # Checkout the repository to the runner + - name: Checkout repository + uses: actions/checkout@v3 + + # Set up Node.js environment + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + # Install client dependencies + - name: Install client dependencies + working-directory: src/app/client + run: yarn install --no-progress --production=true + + # Build the client + - name: Build client + working-directory: src/app/client + run: npm run build + + # List all files after the build + - name: List all files after build + working-directory: src/app/client + run: find . -type f + + # Check if the dist directory exists and list its contents + - name: Check if dist directory exists and list contents + working-directory: src/app/client + run: | + if [ -d dist ]; then + echo "dist directory exists. Listing contents:" + ls -l dist + else + echo "dist directory does not exist" + fi + + # Move index.html to index.ejs if it exists + - name: Move index.html to index.ejs if it exists + working-directory: src/app/client + run: | + if [ -f dist/index.html ]; then + mv dist/index.html dist/index.ejs + else + echo "File dist/index.html does not exist" + fi + + # Set up server directories + - name: Set up server directories + run: mkdir -p $GITHUB_WORKSPACE/app_dist + + # Copy server files to the destination directory + - name: Copy server files + run: | + cp -r src/app/libs src/app/helpers src/app/proxy src/app/resourcebundles src/app/package.json src/app/framework.config.js src/app/sunbird-plugins src/app/routes src/app/constants src/app/controllers src/app/server.js $GITHUB_WORKSPACE/app_dist/ + shell: /usr/bin/bash -e {0} + + # Install server dependencies + - name: Install server dependencies + working-directory: ${{ github.workspace }}/app_dist + run: yarn install --ignore-engines --no-progress --production=true + + # Run server build script + - name: Run server build script + working-directory: ${{ github.workspace }}/app_dist + run: node helpers/resourceBundles/build.js -task="phraseAppPull" + # Execute test cases using JEST + - name: Execute test cases using JEST + working-directory: src/app/client + run: | + yarn config set ignore-engines true + yarn install + npm run test:ci + + # Install Sonar Scanner + - name: Install Sonar Scanner + run: | + cd /tmp + wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip + sudo apt-get install -y unzip + unzip sonar-scanner-cli-5.0.1.3006-linux.zip + cd - + + - name: Run Sonar Scanner + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + /tmp/sonar-scanner-5.0.1.3006-linux/bin/sonar-scanner \ + -Dsonar.projectKey=Sunbird-Ed_SunbirdEd-portal \ + -Dsonar.organization=sunbird-ed \ + -Dsonar.host.url=https://sonarcloud.io \ + -Dsonar.login=$SONAR_TOKEN diff --git a/README.md b/README.md index deb9ce71d6d..e172d5c4d21 100644 --- a/README.md +++ b/README.md @@ -223,10 +223,10 @@ Installing Sunbird requires two primary software components: | Plugin Name | Plugin Repository | npm version | NG Version | |-------------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------ |------------- |------------ | -| [@project-sunbird/chatbot-client](https://www.npmjs.com/package/@project-sunbird/chatbot-client) | https://github.com/project-sunbird/sunbird-bot-client | 4.0.0 | NG 13 | -| [@project-sunbird/common-consumption](https://www.npmjs.com/package/@project-sunbird/common-consumption) | https://github.com/Sunbird-Ed/SunbirdEd-consumption-ngcomponents | 6.0.0 | NG 13 | -| [@project-sunbird/common-form-elements-full](https://www.npmjs.com/package/@project-sunbird/common-form-elements-full) | https://github.com/Sunbird-Ed/SunbirdEd-forms | 6.0.0 | NG 14 | -| [@project-sunbird/sb-content-section](https://www.npmjs.com/package/@project-sunbird/sb-content-section) | https://github.com/Sunbird-Ed/sb-content-module | 6.0.0 | NG 13 | -| [@project-sunbird/sb-notification](https://www.npmjs.com/package/@project-sunbird/sb-notification) | https://github.com/Sunbird-Ed/sb-notification | 6.0.0 | NG 14 | -| [@shikshalokam/sl-questionnaire](https://www.npmjs.com/package/@shikshalokam/sl-questionnaire) | https://github.com/shikshalokam/sl-questionnaire-components | 2.3.0 | NG 12 | -| [@shikshalokam/sl-reports-library](https://www.npmjs.com/package/@shikshalokam/sl-reports-library) | https://github.com/shikshalokam/sl-reports-library | 3.0.1 | NG 14 | \ No newline at end of file +| [@project-sunbird/chatbot-client](https://www.npmjs.com/package/@project-sunbird/chatbot-client) | https://github.com/project-sunbird/sunbird-bot-client | 8.0.1 | NG 16 | +| [@project-sunbird/common-consumption](https://www.npmjs.com/package/@project-sunbird/common-consumption) | https://github.com/Sunbird-Ed/SunbirdEd-consumption-ngcomponents | 8.0.2 | NG 16 | +| [@project-sunbird/common-form-elements-full](https://www.npmjs.com/package/@project-sunbird/common-form-elements-full) | https://github.com/Sunbird-Ed/SunbirdEd-forms | 8.0.1 | NG 16 | +| [@project-sunbird/sb-content-section](https://www.npmjs.com/package/@project-sunbird/sb-content-section) | https://github.com/Sunbird-Ed/sb-content-module | 8.0.1 | NG 16 | +| [@project-sunbird/sb-notification](https://www.npmjs.com/package/@project-sunbird/sb-notification) | https://github.com/Sunbird-Ed/sb-notification | 8.0.1 | NG 16 | +| [@shikshalokam/sl-questionnaire](https://www.npmjs.com/package/@shikshalokam/sl-questionnaire) | https://github.com/shikshalokam/sl-questionnaire-components | 7.0.0 | NG 16 | +| [@shikshalokam/sl-reports-library](https://www.npmjs.com/package/@shikshalokam/sl-reports-library) | https://github.com/shikshalokam/sl-reports-library | 6.0.0 | NG 16 | \ No newline at end of file diff --git a/src/app/client/package.json b/src/app/client/package.json index 73aa746c577..e80dcfe5616 100644 --- a/src/app/client/package.json +++ b/src/app/client/package.json @@ -27,7 +27,8 @@ "inject-cdn-fallback": "gulp inject:cdnFallBack:script", "with-stats": "node --max_old_space_size=8096 node_modules/.bin/ng build --configuration production --stats-json", "build:stats": "ng build --stats-json", - "analyze": "webpack-bundle-analyzer dist/stats.json" + "analyze": "webpack-bundle-analyzer dist/stats.json", + "test-for-file": "./node_modules/jest/bin/jest.js --clearCache && node --max_old_space_size=2048 ./node_modules/jest/bin/jest.js telemetry-interact.directive.spec --ci --collectCoverage=true --coverageReporters=lcov" }, "nodemonConfig": { "ext": "png,jpeg,js,ts,html,json,css,scss" @@ -63,31 +64,31 @@ "@project-sunbird/common-consumption": "8.0.2", "@project-sunbird/common-form-elements-full": "8.0.1", "@project-sunbird/discussions-ui": "8.0.1", - "@project-sunbird/ng2-semantic-ui": "8.0.2", + "@project-sunbird/ng2-semantic-ui": "^8.0.3", "@project-sunbird/sb-content-section": "8.0.1", "@project-sunbird/sb-dashlet": "8.0.2", "@project-sunbird/sb-notification": "8.0.1", - "@project-sunbird/sb-styles": "0.0.16", + "@project-sunbird/sb-styles": "0.0.17", "@project-sunbird/sb-svg2pdf": "8.0.1", - "@project-sunbird/sb-themes": "8.0.5", - "@project-sunbird/sunbird-collection-editor": "5.4.10", + "@project-sunbird/sb-themes": "8.0.8", + "@project-sunbird/sunbird-collection-editor": "6.1.0", "@project-sunbird/sunbird-epub-player-v9": "5.6.0", "@project-sunbird/sunbird-epub-player-web-component": "1.2.0", "@project-sunbird/sunbird-file-upload-library": "1.0.2", "@project-sunbird/sunbird-pdf-player-v9": "5.5.0", "@project-sunbird/sunbird-pdf-player-web-component": "1.4.0", - "@project-sunbird/sunbird-questionset-editor": "8.0.1", + "@project-sunbird/sunbird-questionset-editor": "8.0.3", "@project-sunbird/sunbird-quml-player": "7.0.3", "@project-sunbird/sunbird-quml-player-v9": "5.1.5", - "@project-sunbird/sunbird-quml-player-web-component": "4.0.0", - "@project-sunbird/sunbird-resource-library": "8.0.0", + "@project-sunbird/sunbird-quml-player-web-component": "4.0.2", + "@project-sunbird/sunbird-resource-library": "8.0.2", "@project-sunbird/sunbird-video-player-v9": "5.5.1", "@project-sunbird/sunbird-video-player-web-component": "1.1.1", "@project-sunbird/telemetry-sdk": "1.3.0", "@project-sunbird/web-extensions": "8.0.1", - "@samagra-x/uci-console": "6.0.3", + "uci-console-v16": "8.0.0", "@shikshalokam/sl-questionnaire": "7.0.0", - "@shikshalokam/sl-reports-library": "3.0.1", + "@shikshalokam/sl-reports-library": "6.0.0", "@swimlane/ngx-datatable": "20.1.0", "@types/jquery": "3.3.31", "@types/jquery.fancytree": "2.7.34", @@ -244,4 +245,4 @@ "jest-preset-angular/build/serializers/html-comment" ] } -} +} \ No newline at end of file diff --git a/src/app/client/src/app/modules/core/components/content-type/content-type.component.scss b/src/app/client/src/app/modules/core/components/content-type/content-type.component.scss index 7bb4244bf4d..dcbf4a60667 100644 --- a/src/app/client/src/app/modules/core/components/content-type/content-type.component.scss +++ b/src/app/client/src/app/modules/core/components/content-type/content-type.component.scss @@ -29,7 +29,7 @@ border-radius: calculateRem(6px); box-shadow: var(--sbt-box-shadow-6px); font-size: calculateRem(12px); - font-weight: bold; + font-weight: 500; margin-right: calculateRem(12px); height: calculateRem(48px); transition: all ease .3s; diff --git a/src/app/client/src/app/modules/core/services/data/data.service.spec.ts b/src/app/client/src/app/modules/core/services/data/data.service.spec.ts index d6b92fe6f97..05976376f37 100644 --- a/src/app/client/src/app/modules/core/services/data/data.service.spec.ts +++ b/src/app/client/src/app/modules/core/services/data/data.service.spec.ts @@ -86,7 +86,7 @@ describe('DataService', () => { it('should return observableThrowError for failed POST request', (done) => { const mockErrorResponse = { responseCode: 'ERROR', data: 'Error data' }; - jest.spyOn(mockHttpClient, 'post').mockReturnValue(throwError(mockErrorResponse)); + jest.spyOn(mockHttpClient, 'post').mockReturnValue(of(mockErrorResponse)); (dayjs as any).mockReturnValue({ format: jest.fn(() => 'formatDate') }); dataService.post({ url: '/mock-url', @@ -114,7 +114,7 @@ describe('DataService', () => { it('should handle error for failed PATCH request', (done) => { const mockErrorResponse = { responseCode: 'ERROR', data: 'Error data' }; - jest.spyOn(mockHttpClient, 'patch').mockReturnValue(throwError(mockErrorResponse)); + jest.spyOn(mockHttpClient, 'patch').mockReturnValue(of(mockErrorResponse)); (dayjs as any).mockReturnValue({ format: jest.fn(() => 'formatDate') }); dataService.patch({ url: '/mock-url', @@ -142,7 +142,7 @@ describe('DataService', () => { it('should handle error for failed DELETE request', (done) => { const mockErrorResponse = { responseCode: 'ERROR', data: 'Error data' }; - jest.spyOn(mockHttpClient, 'delete').mockReturnValue(throwError(mockErrorResponse)); + jest.spyOn(mockHttpClient, 'delete').mockReturnValue(of(mockErrorResponse)); (dayjs as any).mockReturnValue({ format: jest.fn(() => 'formatDate') }); dataService.delete({ url: '/mock-url', @@ -170,7 +170,7 @@ describe('DataService', () => { it('should handle error for failed PUT request', (done) => { const mockErrorResponse = { responseCode: 'ERROR', data: 'Error data' }; - jest.spyOn(mockHttpClient, 'put').mockReturnValue(throwError(mockErrorResponse)); + jest.spyOn(mockHttpClient, 'put').mockReturnValue(of(mockErrorResponse)); (dayjs as any).mockReturnValue({ format: jest.fn(() => 'formatDate') }); dataService.put({ url: '/mock-url', @@ -286,5 +286,25 @@ describe('DataService', () => { }); }); }); + it('should handle successful POST request with headers', async () => { + jest.spyOn(document, 'getElementById').mockImplementation((data) => { + return { value: '32cef05697d1ab3a8049c0e8981bcc79' } as any; + }); + dataService.rootOrgId = '1234567890'; + dataService.channelId = '1234567890'; + DataService.userId = 'user_123'; + const mockResponse = { responseCode: 'OK', data: 'Mock data' }; + const mockHeaders = { get: jest.fn(() => '2023-01-01T12:00:00Z') }; + + jest.spyOn(mockHttpClient, 'post').mockReturnValue(of({ body: mockResponse, headers: mockHeaders })); + (dayjs as any).mockReturnValue({ format: jest.fn(() => 'formatDate') }); + jest.spyOn(dataService, 'getDateDiff' as any).mockReturnValue(0); + + await dataService.postWithHeaders({ url: '/mock-url', data: { key: 'value' }, header: { 'Custom-Header': 'custom-value' } }).subscribe((response) => { + expect(response).toEqual(mockResponse); + expect(mockHttpClient.post).toHaveBeenCalledWith(expect.stringContaining('/mock-url'), { key: 'value' }, expect.any(Object)); + expect(dataService['getDateDiff']).toHaveBeenCalledWith('2023-01-01T12:00:00Z'); + }); + }); }); \ No newline at end of file diff --git a/src/app/client/src/app/modules/core/services/generalisedLable/generaliseLable.service.spec.data.ts b/src/app/client/src/app/modules/core/services/generalisedLable/generaliseLable.service.spec.data.ts index 2a5161f4916..fa7b1ad9049 100644 --- a/src/app/client/src/app/modules/core/services/generalisedLable/generaliseLable.service.spec.data.ts +++ b/src/app/client/src/app/modules/core/services/generalisedLable/generaliseLable.service.spec.data.ts @@ -32,9 +32,104 @@ export const MockResponse = { courseHierarchy: { contentType: 'Course', primaryCategory: 'Course', - trackable: {enabled: 'Yes'} + trackable: { enabled: 'Yes' } + }, + courseHierarchyNew: { + contentType: 'Course', + primaryCategory: 'Course', + trackable: 'true' + }, + courseHierarchyone:{ + contentType: 'course', + primaryCategory: 'course', + }, + courseHierarchytwo:{ + contentType: 'book', + primaryCategory: 'book', }, generaliseLblResponse: { - 'result': '{"dflt":{"nontrk":{"frmelmnts":{"lbl":{"ActivityTextbooks":" Tasks","chapter":" Learning module"}},"messages":{"stmsg":{"m0125":" Start creating or uploading content. You currently do not have any content saved as a draft "}}},"trk":{"frmelmnts":{"btn":{"create":" Create ","enroll":" Join"},"tab":{"courses":" Tasks"}},"messages":{"dashboard":{"emsg":{"m002":" User has not joined any batch for this learning task"}}}}},"crs":{"nontrk":{"frmelmnts":{"lbl":{"ActivityTextbooks":" Courses","ACTIVITY_TEXTBOOK_TITLE":" Courses","desktop":{"downloadBook":" Download course","find_more":" Find more courses and related content on {instance}","updateTextbook":" Update course"},"downloadBooks":" Download courses to access while offline","fromTheTextBook":" from the course","noBookfoundTitle":" Board is adding courses","textbooks":" Courses","chapter":" Course module"}}},"trk":{"completedCourse":" Course completed","frmelmnts":{"btn":{"createCourse":" Create course"},"lbl":{"accessCourse":" Access course","accessToLogin":" To access the course you have to log in and join the course"}},"messages":{"dashboard":{"emsg":{"m002":" The user is not enrolled in any batch of this course"}}}}}}' + result: + { + 'default': { + 'nontrackable': { + 'en': { + 'frmelmnts': { + 'lbl': { + 'ActivityTextbooks': ' Tasks', + 'chapter': ' Learning module' + } + }, + 'messages': { + 'stmsg': { + 'm0125': ' Start creating or uploading content. You currently do not have any content saved as a draft' + } + } + } + }, + 'trackable': { + 'en': { + 'frmelmnts': { + 'btn': { + 'create': ' Create ', + 'enroll': ' Join' + }, + 'tab': { + 'courses': ' Tasks' + } + }, + 'messages': { + 'dashboard': { + 'emsg': { + 'm002': ' User has not joined any batch for this learning task' + } + } + } + } + } + }, + 'course': { + 'nontrackable': { + 'en': { + 'frmelmnts': { + 'lbl': { + 'ActivityTextbooks': ' Courses', + 'ACTIVITY_TEXTBOOK_TITLE': ' Courses', + 'desktop': { + 'downloadBook': ' Download course', + 'find_more': ' Find more courses and related content on {instance}', + 'updateTextbook': ' Update course' + }, + 'downloadBooks': ' Download courses to access while offline', + 'fromTheTextBook': ' from the course', + 'noBookfoundTitle': ' Board is adding courses', + 'textbooks': ' Courses', + 'chapter': ' Course module' + } + } + } + }, + 'trackable': { + 'en': { + 'completedCourse': ' Course completed', + 'frmelmnts': { + 'btn': { + 'createCourse': ' Create course' + }, + 'lbl': { + 'accessCourse': ' Access course', + 'accessToLogin': ' To access the course you have to log in and join the course' + } + }, + 'messages': { + 'dashboard': { + 'emsg': { + 'm002': ' The user is not enrolled in any batch of this course' + } + } + } + } + } + } + } } }; diff --git a/src/app/client/src/app/modules/core/services/generalisedLable/generaliseLable.service.spec.ts b/src/app/client/src/app/modules/core/services/generalisedLable/generaliseLable.service.spec.ts index a29e014ea2b..0e9509e6753 100644 --- a/src/app/client/src/app/modules/core/services/generalisedLable/generaliseLable.service.spec.ts +++ b/src/app/client/src/app/modules/core/services/generalisedLable/generaliseLable.service.spec.ts @@ -21,14 +21,18 @@ describe('GeneraliseLabelService', () => { }, COURSE_FRAMEWORK: { COURSE_FRAMEWORKID: 'data/v1/system/settings/get/courseFrameworkId' - } + }, + GET_GENERALISED_RESOURCE: 'getGeneralisedResourcesBundles' } } }; const mockUsageService: Partial = { getData: jest.fn() }; - const mockResourceService: Partial = {}; + const mockResourceService: Partial = { + messages:'this is the default message', + frmelmnts:'this is the default frmelmnts' + }; const mockFormService: Partial = { getFormConfig: jest.fn().mockReturnValue(of(MockResponse.resourceBundleConfig)) as any }; @@ -77,15 +81,144 @@ describe('GeneraliseLabelService', () => { generaliseLabelService.getGeneraliseResourceBundle() expect(generaliseLabelService.formService.getFormConfig).toHaveBeenCalled(); }); - }); + }); - it('should initialize with the provided content data and language', () => { + it('should initialize with the provided content data and language', () => { const contentData = {}; const lang = 'en'; const mockGetLabels = jest.spyOn(generaliseLabelService, 'getLabels' as any); - mockGetLabels.mockImplementation(() => {}); + mockGetLabels.mockImplementation(() => { }); generaliseLabelService.initialize(contentData, lang); expect(mockGetLabels).toHaveBeenCalledWith(contentData, lang); }); + it('should initialize with the component and call the fetchGeneraliseLables method', () => { + jest.spyOn(generaliseLabelService.usageService, 'getData').mockReturnValue(of(MockResponse.resourceBundleConfig as any) as any) as any; + generaliseLabelService['fetchGeneraliseLables']('en', 'all_labels_en.json').subscribe(data => { + expect(data).toEqual(MockResponse.resourceBundleConfig); + }); + }); + it('should initialize with the component and call the getResourcedFileName method', () => { + jest.spyOn(generaliseLabelService.usageService, 'getData').mockReturnValue(of(MockResponse.resourceBundleConfig as any) as any) as any; + generaliseLabelService['gResourseBundleForm'] = MockResponse.generaliseLblResponse.result; + const value = generaliseLabelService['getResourcedFileName'](MockResponse.courseHierarchy, 'en') + expect(value).toEqual(MockResponse.generaliseLblResponse.result.course.trackable.en); + }); + it('should initialize with the component and call the getResourcedFileName method for nontrackable', () => { + jest.spyOn(generaliseLabelService.usageService, 'getData').mockReturnValue(of(MockResponse.courseHierarchyNew as any) as any) as any; + generaliseLabelService['gResourseBundleForm'] = MockResponse.generaliseLblResponse.result; + const value = generaliseLabelService['getResourcedFileName'](MockResponse.courseHierarchyNew, 'en') + expect(value).toEqual(MockResponse.generaliseLblResponse.result.course.nontrackable.en); + }); + it('should initialize with the component and call the getResourcedFileName method for the course', () => { + jest.spyOn(generaliseLabelService.usageService, 'getData').mockReturnValue(of(MockResponse.courseHierarchyNew as any) as any) as any; + generaliseLabelService['gResourseBundleForm'] = MockResponse.generaliseLblResponse.result; + const value = generaliseLabelService['getResourcedFileName'](MockResponse.courseHierarchyone, 'ka') + expect(value).toEqual(MockResponse.generaliseLblResponse.result.course.trackable.en); + }); + it('should initialize with the component and call the getResourcedFileName method for other than the course', () => { + jest.spyOn(generaliseLabelService.usageService, 'getData').mockReturnValue(of(MockResponse.courseHierarchyNew as any) as any) as any; + generaliseLabelService['gResourseBundleForm'] = MockResponse.generaliseLblResponse.result; + const value = generaliseLabelService['getResourcedFileName'](MockResponse.courseHierarchytwo, 'ka') + expect(value).toEqual(MockResponse.generaliseLblResponse.result.default.nontrackable.en); + }); + it('should initialize with the component and call the setLabels method', () => { + const labels = { + 'dflt': { + 'nontrk': { + 'frmelmnts': { + 'lbl': { + 'ActivityTextbooks': ' Tasks' + } + }, + 'messages': { + 'stmsg': { + 'm0125': ' Start creating or uploading content.' + } + } + } + }, + 'book': { + 'nontrk': { + 'frmelmnts': { + 'lbl': { + 'ActivityTextbooks': ' Tasks' + } + }, + 'messages': { + 'stmsg': { + 'm0125': ' Start creating or uploading content.' + } + } + } + } + } + generaliseLabelService['contentTypeLblKey'] = 'book'; + generaliseLabelService['setLabels'](labels); + expect(generaliseLabelService.messages).toEqual({ + stmsg: { + m0125: ' Start creating or uploading content.' + } + } + ) + expect(generaliseLabelService.frmelmnts).toEqual({ lbl: { ActivityTextbooks: ' Tasks'} }); + }); + it('should initialize with the component and call the setLabels method isTrackable as trackable', () => { + generaliseLabelService['isTrackable']='trackable' + const labels = { + 'dflt': { + 'trk': { + 'frmelmnts': { + 'lbl': { + 'ActivityTextbooks': ' Tasks' + } + }, + 'messages': { + 'dashboard': { + 'emsg': { + 'm002': ' User has not joined any batch for this learning task' + } + } + } + } + }, + 'book': { + 'trk': { + 'frmelmnts': { + 'lbl': { + 'ActivityTextbooks': ' Tasks' + } + }, + 'messages': { + 'dashboard': { + 'emsg': { + 'm002': ' User has not joined any batch for this learning task' + } + } + } + } + } + } + generaliseLabelService['contentTypeLblKey'] = 'book'; + generaliseLabelService['setLabels'](labels); + expect(generaliseLabelService.messages).toEqual({'dashboard': { + 'emsg': { + 'm002': ' User has not joined any batch for this learning task' + } + }}) + expect(generaliseLabelService.frmelmnts).toEqual({ lbl: { ActivityTextbooks: ' Tasks'} }); + }); + it('should initialize with the component and call the setLabels method with default value', () => { + generaliseLabelService['isTrackable']='trackable' + const labels = { + } + generaliseLabelService['contentTypeLblKey'] = 'book'; + generaliseLabelService['setLabels'](labels); + expect(generaliseLabelService.messages).toEqual('this is the default message') + expect(generaliseLabelService.frmelmnts).toEqual('this is the default frmelmnts'); + }); + xit('should initialize with the component and call the getLabels method ', () => { + generaliseLabelService['getLabels'](MockResponse.courseHierarchy,'en'); + + }); }); \ No newline at end of file diff --git a/src/app/client/src/app/modules/core/services/otp/otp.service.spec.ts b/src/app/client/src/app/modules/core/services/otp/otp.service.spec.ts index b7c3ff70045..7cae427c6f3 100644 --- a/src/app/client/src/app/modules/core/services/otp/otp.service.spec.ts +++ b/src/app/client/src/app/modules/core/services/otp/otp.service.spec.ts @@ -10,10 +10,10 @@ describe('OtpService', () => { urlConFig: { URLS: { OTP: { - GENERATE: 'otp/v1/generate', - VERIFY: 'otp/v1/verify', + GENERATE: 'otp/v2/generate', + VERIFY: 'otp/v2/verify', ANONYMOUS:{ - GENERATE_USERDELETE:'anonymous/delete/otp/v1/generate' + GENERATE_USERDELETE:'anonymous/delete/otp/v2/generate' } } } diff --git a/src/app/client/src/app/modules/core/services/permission/permission.mock.spec.data.ts b/src/app/client/src/app/modules/core/services/permission/permission.mock.spec.data.ts index a4e3838b119..484c1f9cbfd 100644 --- a/src/app/client/src/app/modules/core/services/permission/permission.mock.spec.data.ts +++ b/src/app/client/src/app/modules/core/services/permission/permission.mock.spec.data.ts @@ -330,6 +330,188 @@ export const mockPermissionRes = { } ] } + }, + rolelist: [ + { + "name": "Book Creator", + "id": "BOOK_CREATOR" + }, + { + "name": "Membership Management", + "id": "MEMBERSHIP_MANAGEMENT" + }, + { + "name": "Flag Reviewer", + "id": "FLAG_REVIEWER" + }, + { + "name": "Report Viewer", + "id": "REPORT_VIEWER" + }, + { + "name": "Program Manager", + "id": "PROGRAM_MANAGER" + }, + { + "name": "Program Designer", + "id": "PROGRAM_DESIGNER" + }, + { + "name": "System Administration", + "id": "SYSTEM_ADMINISTRATION" + }, + { + "name": "Content Curation", + "id": "CONTENT_CURATION" + }, + { + "name": "Book Reviewer", + "id": "BOOK_REVIEWER" + }, + { + "name": "Content Creator", + "id": "CONTENT_CREATOR" + }, + { + "name": "Org Management", + "id": "ORG_MANAGEMENT" + }, + { + "name": "Course Admin", + "id": "COURSE_ADMIN" + }, + { + "name": "Org Moderator", + "id": "ORG_MODERATOR" + }, + { + "name": "Public", + "id": "PUBLIC" + }, + { + "name": "Admin", + "id": "ADMIN" + }, + { + "name": "Course Mentor", + "id": "COURSE_MENTOR" + }, + { + "name": "Content Reviewer", + "id": "CONTENT_REVIEWER" + }, + { + "name": "Report Admin", + "id": "REPORT_ADMIN" + }, + { + "name": "Org Admin", + "id": "ORG_ADMIN" + } + ], + roles:[ + 'COURSE_MENTOR', + 'CONTENT_CREATOR', + 'COURSE_ADMIN', + 'BOOK_CREATOR', + 'ORG_ADMIN', + 'REPORT_ADMIN', + 'CONTENT_REVIEWER', + 'ADMIN', + 'PUBLIC' + ], + RolesAndPermissions:[ + { + "roleName": "Book Creator", + "role": "BOOK_CREATOR", + 'actions': [ + { + 'urls': [ + 'v1/Book/create' + ], + 'name': 'Book Creator', + 'id': 'BOOK_CREATOR' + } + + ] + }, + { + "roleName": "Book Reviewer", + "role": "BOOK_REVIEWER", + 'actions': [ + { + 'urls': [ + 'v1/Book/review' + ], + 'name': 'Book Reviewer', + 'id': 'BOOK_REVIEWER' + } + + ] + }, + { + "roleName": "Content Creator", + "role": "CONTENT_CREATOR", + 'actions': [ + { + 'urls': [ + 'v1/course/create' + ], + 'name': 'Course Creator', + 'id': 'CONTENT_CREATOR' + } + + ] } + ], + WORKSPACEAUTHGARDROLES:[ + { + "roles":["CONTENT_CREATOR","CONTENT_CREATION","CONTENT_REVIEWER","CONTENT_REVIEW","BOOK_CREATOR"], + "roleName":"createRole","url":"workspace/content/create","tab":"CREATE" + }, + { + "roles":["ORG_ADMIN"], + "roleName":"alltextbookRole","url":"workspace/content/alltextbooks","tab":"ALLTEXTBOOKS" + }, + { + "roles":["CONTENT_CREATOR","CONTENT_CREATION","CONTENT_REVIEWER","CONTENT_REVIEW","BOOK_CREATOR"], + "roleName":"draftRole","url":"workspace/content/draft/1","tab":"DRAFTS" + }, + { + "roles":["CONTENT_CREATOR","CONTENT_CREATION","CONTENT_REVIEWER","CONTENT_REVIEW","BOOK_CREATOR"], + "roleName":"inreviewRole","url":"/workspace/content/review/1","tab":"Review Submissions" + }, + { + "roles":["CONTENT_CREATOR","CONTENT_CREATION","CONTENT_REVIEWER","CONTENT_REVIEW","BOOK_CREATOR"], + "roleName":"publishedRole","url":"workspace/content/published/1","tab":"PUBLISHED" + }, + { + "roles":["CONTENT_CREATOR","CONTENT_CREATION","CONTENT_REVIEWER","CONTENT_REVIEW"], + "roleName":"alluploadsRole","url":"workspace/content/uploaded/1","tab":"All UPLOADS" + }, + { + "roles":["CONTENT_REVIEWER","CONTENT_REVIEW","BOOK_REVIEWER"], + "roleName":"upForReviewRole","url":"workspace/content/upForReview/1","tab":"Up FOR REVIEW" + }, + { + "roles":["COURSE_MENTOR"],"roleName":"courseBatchRoles","url":"/workspace/content/batches/1","tab":"Course Batches" + }, + { + "roles":["FLAG_REVIEWER"], + "roleName":"flagReviewerRole","url":"workspace/content/flagreviewer/1","tab":"FLAG REVIEWER" + }, + { + "roles":["FLAG_REVIEWER"], + "roleName":"flaggedRole","url":"workspace/content/flagged/1","tab":"Flagged" + }, + { + "roles":["CONTENT_CREATOR","CONTENT_CREATION","CONTENT_REVIEWER","CONTENT_REVIEW","BOOK_CREATOR"], + "roleName":"limitedPublishingRole","url":"workspace/content/upForReview/1","tab":"Limited Publishing" + }, + { + "roles":["CONTENT_CREATOR","CONTENT_CREATION","CONTENT_REVIEWER","CONTENT_REVIEW","BOOK_CREATOR"], + "roleName":"collaboratingRole","url":"workspace/content/collaborating-on","tab":"Collaborating On" + } + ], }; diff --git a/src/app/client/src/app/modules/core/services/permission/permission.service.spec.ts b/src/app/client/src/app/modules/core/services/permission/permission.service.spec.ts new file mode 100644 index 00000000000..a467fb3c79c --- /dev/null +++ b/src/app/client/src/app/modules/core/services/permission/permission.service.spec.ts @@ -0,0 +1,127 @@ + +/** +* Description. +* This spec file was created using ng-test-barrel plugin! +* +*/ + +import { ConfigService, ServerResponse, ToasterService, ResourceService, IUserData } from '@sunbird/shared'; +import { LearnerService } from './../learner/learner.service'; +import { UserService } from '../user/user.service'; +import { Injectable } from '@angular/core'; +import { _ } from 'lodash-es'; +import { RolesAndPermissions, Roles } from './../../interfaces'; +import { Observable, BehaviorSubject, of } from 'rxjs'; +import { shareReplay, tap } from 'rxjs/operators'; +import { PermissionService } from './permission.service'; +import { mockPermissionRes } from './permission.mock.spec.data' + +describe('PermissionService', () => { + let component: PermissionService; + + const resourceService: Partial = {}; + const config: Partial = { + urlConFig: { + URLS: { + ROLES: { + READ: "data/v1/role/read", + } + }, + }, + rolesConfig:{ + WORKSPACEAUTHGARDROLES: mockPermissionRes.WORKSPACEAUTHGARDROLES + } + }; + const learner: Partial = { + get: jest.fn().mockReturnValue(of(mockPermissionRes.success)) + }; + const userService: Partial = { + loggedIn: true, + slug: jest.fn().mockReturnValue('tn') as any, + userData$: of({ + userProfile: { + userId: 'sample-uid', + rootOrgId: 'sample-root-id', + rootOrg: {}, + hashTagIds: ['id'], + userRoles:['BOOK_REVIEWER'] + } as any, + }) as any, + setIsCustodianUser: jest.fn(), + setGuestUser: jest.fn(), + userid: 'sample-uid', + appId: 'sample-id', + getServerTimeDiff: '', + }; + const toasterService: Partial = {}; + + beforeAll(() => { + component = new PermissionService( + resourceService as ResourceService, + config as ConfigService, + learner as LearnerService, + userService as UserService, + toasterService as ToasterService + ) + component.mainRoles = mockPermissionRes.RolesAndPermissions; + component.rolesAndPermissions = mockPermissionRes.RolesAndPermissions; + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + + it('should create a instance of component', () => { + expect(component).toBeTruthy(); + }); + it('should create a instance of component and call the initialize method', () => { + const privateMethodSpy = jest.spyOn(component,['setCurrentRoleActions'] as any) as any; + component.initialize() + expect(component).toBeTruthy(); + expect(privateMethodSpy).toBeCalled(); + }); + it('should create a instance of component and call the getPermissionsData method', () => { + jest.spyOn(learner,'get').mockReturnValue(of(mockPermissionRes.success as any)) as any + component['getPermissionsData'](); + expect(component).toBeTruthy(); + }); + it('should create a instance of component and call the checkRolesPermissions method', () => { + const value = component.checkRolesPermissions(mockPermissionRes.roles); + expect(component).toBeTruthy(); + expect(value).toBeFalsy(); + }); + it('should create a instance of component and call the checkRolesPermissions method', () => { + userService.userData$.subscribe((data) => { + data.userProfile.userRoles = ['PUBLIC']; + }); + component.userRoles = ['PUBLIC', 'BOOK_CREATOR'] + component.initialize(); + const value = component.checkRolesPermissions(mockPermissionRes.roles); + expect(component).toBeTruthy(); + expect(value).toBeTruthy(); + }); + it('should create a instance of component and call the allRoles method', () => { + const value = component.allRoles; + expect(component).toBeTruthy(); + expect(JSON.stringify(value)).toEqual(JSON.stringify(mockPermissionRes.RolesAndPermissions)); + }); + it('should create a instance of component and call the getWorkspaceAuthRoles method', () => { + const value = component.getWorkspaceAuthRoles(); + expect(component).toBeTruthy(); + expect(JSON.stringify(value)).toEqual(JSON.stringify(mockPermissionRes.WORKSPACEAUTHGARDROLES[0])) + }); + it('should create a instance of component and call the setCurrentRoleActions method', () => { + component.rolesAndPermissions = mockPermissionRes.RolesAndPermissions; + component['setCurrentRoleActions'](); + expect(component).toBeTruthy(); + expect(component.permissionAvailable).toBeTruthy(); + }); + xit('should create a instance of component and call the setCurrentRoleActions method with user error', () => { + userService.userData$.subscribe((data) => { + data.err = mockPermissionRes.error as any; + component['setCurrentRoleActions'](); + }); + }); + +}); \ No newline at end of file diff --git a/src/app/client/src/app/modules/core/services/tenant/tenant.service.spec.ts b/src/app/client/src/app/modules/core/services/tenant/tenant.service.spec.ts index 90e8c404cb7..896e1a7c7ca 100644 --- a/src/app/client/src/app/modules/core/services/tenant/tenant.service.spec.ts +++ b/src/app/client/src/app/modules/core/services/tenant/tenant.service.spec.ts @@ -12,8 +12,8 @@ describe('TenantService', () => { urlConFig: { URLS: { OTP: { - GENERATE: 'otp/v1/generate', - VERIFY: 'otp/v1/verify', + GENERATE: 'otp/v2/generate', + VERIFY: 'otp/v2/verify', }, TENANT: { INFO: 'sunbird' diff --git a/src/app/client/src/app/modules/core/services/user/user.service.spec.ts b/src/app/client/src/app/modules/core/services/user/user.service.spec.ts index fe87f913988..57d3cd14ee9 100644 --- a/src/app/client/src/app/modules/core/services/user/user.service.spec.ts +++ b/src/app/client/src/app/modules/core/services/user/user.service.spec.ts @@ -19,8 +19,8 @@ describe('UserService', () => { }, URLS: { OTP: { - GENERATE: 'otp/v1/generate', - VERIFY: 'otp/v1/verify', + GENERATE: 'otp/v2/generate', + VERIFY: 'otp/v2/verify', }, USER: { GET_PROFILE: 'user/v5/read/', @@ -31,11 +31,11 @@ describe('UserService', () => { TNC_ACCEPT: 'user/v1/tnc/accept' }, OFFLINE: { - READ_USER: 'desktop/user/v1/read', - CREATE_USER: 'desktop/user/v1/create', - UPDATE_USER: 'desktop/user/v1/update', + READ_USER: 'desktop/user/v5/read', + CREATE_USER: 'desktop/user/v1/sso/create', + UPDATE_USER: 'desktop/user/v3/update', GET_USER_BY_KEY: 'desktop/user/v1/read/by/key', - USER_EXISTS_GET_USER_BY_KEY: 'desktop/user/v1/exists/by/key' + USER_EXISTS_GET_USER_BY_KEY: 'desktop/user/v2/exists/by/key' } } } diff --git a/src/app/client/src/app/modules/dashboard/components/data-chart/data-chart.component.html b/src/app/client/src/app/modules/dashboard/components/data-chart/data-chart.component.html index f915da9e355..48533ece1a0 100644 --- a/src/app/client/src/app/modules/dashboard/components/data-chart/data-chart.component.html +++ b/src/app/client/src/app/modules/dashboard/components/data-chart/data-chart.component.html @@ -109,42 +109,40 @@
-
- diff --git a/src/app/client/src/app/modules/dashboard/components/data-chart/data-chart.component.ts b/src/app/client/src/app/modules/dashboard/components/data-chart/data-chart.component.ts index e4f339d13b6..206085e0e3e 100644 --- a/src/app/client/src/app/modules/dashboard/components/data-chart/data-chart.component.ts +++ b/src/app/client/src/app/modules/dashboard/components/data-chart/data-chart.component.ts @@ -490,6 +490,7 @@ export class DataChartComponent implements OnInit, OnDestroy { if (this.filterPopUpMat) { this.dialogRef = this.dialog.open(this.filterPopUpMat, { data: this.chartData['selectedFilters'], + panelClass: 'material-modal' }); } } diff --git a/src/app/client/src/app/modules/dashboard/components/list-all-reports/list-all-reports.component.spec.ts b/src/app/client/src/app/modules/dashboard/components/list-all-reports/list-all-reports.component.spec.ts new file mode 100644 index 00000000000..624271e1c19 --- /dev/null +++ b/src/app/client/src/app/modules/dashboard/components/list-all-reports/list-all-reports.component.spec.ts @@ -0,0 +1,375 @@ +import { ListAllReportsComponent } from './list-all-reports.component'; +import { UserService, TncService } from '@sunbird/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NavigationHelperService, LayoutService, ResourceService } from '@sunbird/shared'; +import { TelemetryService } from '@sunbird/telemetry'; +import { Location } from '@angular/common'; +import { of } from 'rxjs'; +import { ReportService } from '../../services'; +import { ElementRef } from '@angular/core'; + + +describe('ListAllReportsComponent', () => { + let component: ListAllReportsComponent; + const mockResourceService: Partial = {} + const mockUserService: Partial = { + userData$: of({ + userProfile: { + userId: 'sample-uid', + rootOrgId: 'sample-root-id', + rootOrg: {}, + hashTagIds: ['id'], + + } as any, + subscribe: jest.fn() + }) as any, + }; + const mockReportService: Partial = { + isAuthenticated: jest.fn().mockReturnValue(of(true)), + isUserReportAdmin: jest.fn().mockReturnValue(true), + isUserSuperAdmin: jest.fn().mockReturnValue(false), + listAllReports: jest.fn().mockReturnValue(of({ reports: [{ report_type: 'Report' }, { report_type: 'Dataset' }] })), + getMaterializedChildRows: jest.fn(), + getFlattenedReports: jest.fn() + }; + const mockActivatedRoute: Partial = { + snapshot: { + data: { + telemetry: { + env: 'test-env', + pageid: 'test-pageid' + }, + roles: ['admin'] + } + } as any, + }; + const mockRouter: Partial = { + navigate: jest.fn().mockResolvedValue(true), + url: '/test-url' + }; + const mockNavigationHelperService: Partial = { + getPageLoadTime: jest.fn().mockReturnValue(500) + }; + const mockTelemetryService: Partial = { + interact: jest.fn(), + }; + const mockTncService: Partial = { + getReportViewerTnc: jest.fn().mockReturnValue(of({ + result: { + response: { + value: '{ "latestVersion": "1.0","1.0": "abc","abc": {"url": "http://example.com"}}' + } + } + })) + }; + const mockLocation: Partial = { + back: jest.fn() + }; + const mockLayoutService: Partial = { + initlayoutConfig: jest.fn(), + switchableLayout: jest.fn().mockReturnValue(of({ layout: 'mockLayout' })) + }; + + beforeEach(() => { + component = new ListAllReportsComponent( + mockResourceService as ResourceService, + mockReportService as ReportService, + mockActivatedRoute as ActivatedRoute, + mockRouter as Router, + mockUserService as UserService, + mockNavigationHelperService as NavigationHelperService, + mockTelemetryService as TelemetryService, + mockLayoutService as LayoutService, + mockTncService as TncService, + mockLocation as Location, + + ) + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should set userProfile and call getReportViewerTncPolicy if user and userProfile exist', () => { + const mockUser = { + userProfile: { + userId: 'sample-uid', + rootOrgId: 'sample-root-id', + rootOrg: {}, + hashTagIds: ['id'], + + } as any, + }; + const getReportViewerTncPolicySpy = jest.spyOn(component, 'getReportViewerTncPolicy'); + component.ngOnInit(); + expect(component.userProfile).toEqual(mockUser.userProfile); + expect(getReportViewerTncPolicySpy).toHaveBeenCalled(); + }); + + it('should set reportsList$ based on authentication and user role', () => { + mockReportService.isAuthenticated = jest.fn().mockReturnValue(of(true)); + mockReportService.isUserReportAdmin = jest.fn().mockReturnValue(true); + const mockReports = [{ id: 1, name: 'Report 1' }, { id: 2, name: 'Report 2' }]; + component.ngOnInit(); + expect(component.reportsList$).toBeDefined(); + component.reportsList$.subscribe(reports => { + expect(reports).toEqual(mockReports); + }); + expect(mockReportService.isAuthenticated).toHaveBeenCalledWith(['admin']); + expect(mockReportService.isUserReportAdmin).toHaveBeenCalled(); + // expect(component.getReportsList).toHaveBeenCalledWith(true); + }); + + + it('should navigate to the report with reportId and optional hash', () => { + // Arrange + const reportId = 'testReportId'; + const hash = 'testHash'; + const materialize = true; + // Act + component.rowClickEventHandler(reportId, hash, materialize); + // Assert + expect(mockRouter.navigate).toHaveBeenCalledWith( + ['/dashBoard/reports', reportId, hash], + { queryParams: { materialize } } + ); + }); + + it('should navigate to the report without optional hash', () => { + // Arrange + const reportId = 'testReportId'; + // Act + component.rowClickEventHandler(reportId); + // Assert + expect(mockRouter.navigate).toHaveBeenCalledWith( + ['/dashBoard/reports', reportId], + { queryParams: {} } + ); + }); + + it('should set reportViewerTncVersion and reportViewerTncUrl', () => { + component.getReportViewerTncPolicy(); + expect(component.reportViewerTncVersion).toEqual('1.0'); + expect(component.reportViewerTncUrl).toEqual(undefined); + }); + + it('should set showTncPopup to true if reportViewerTncObj is not present in userProfile', () => { + // Arrange + component.userProfile = { allTncAccepted: {} }; + // Act + component.showReportViewerTncForFirstUser(); + // Assert + expect(component.showTncPopup).toBeTruthy(); + }); + + it('should not set showTncPopup to true if reportViewerTncObj is present in userProfile', () => { + // Arrange + component.userProfile = { allTncAccepted: { reportViewerTnc: true } }; + // Act + component.showReportViewerTncForFirstUser(); + // Assert + expect(component.showTncPopup).toBeFalsy(); + }); + + it('should call location.back() when goBack() is called', () => { + component.goBack(); + expect(mockLocation.back).toHaveBeenCalled(); + }); + + it('should update layoutConfiguration based on switchableLayout observable', () => { + component.initLayout(); + expect(component.layoutConfiguration).toBe('mockLayout'); + }); + + + it('should return the telemetry impression object', () => { + // Arrange + const type = 'test-type'; + const cdata = [{ id: 'test-id', type: 'test-type' }]; + // Act + const telemetryImpression = component.getTelemetryImpression({ type, cdata }); + // Assert + expect(telemetryImpression).toEqual({ + context: { + env: 'test-env', + cdata: [{ id: 'test-id', type: 'test-type' }] + }, + object: { + id: undefined, + type: 'user', + ver: '1.0' + }, + edata: { + type: 'test-type', + pageid: 'test-pageid', + uri: '/test-url', + duration: 500 + } + }); + }); + + it('should return correct reports count for different scenarios', () => { + const reports = [ + { id: 1, status: 'draft' }, + { id: 2, status: 'live' }, + { id: 3, status: 'draft' }, + { id: 4, status: 'archived' } + ]; + const draftReportsCount = component['getReportsCount']({ reports, status: 'draft' }); + const liveReportsCount = component['getReportsCount']({ reports, status: 'live' }); + const archivedReportsCount = component['getReportsCount']({ reports, status: 'archived' }); + expect(draftReportsCount).toBe(2); + expect(liveReportsCount).toBe(1); + expect(archivedReportsCount).toBe(1); + }); + + + it('should return 0 when reports array is empty', () => { + const reports = []; + const count = component['getReportsCount']({ reports }); + expect(count).toBe(0); + }); + + it('should return correct count when reports array contains elements with specified status', () => { + const reports = [ + { id: 1, status: 'draft' }, + { id: 2, status: 'live' }, + { id: 3, status: 'draft' }, + { id: 4, status: 'archived' } + ]; + const countDraft = component['getReportsCount']({ reports, status: 'draft' }); + const countLive = component['getReportsCount']({ reports, status: 'live' }); + const countArchived = component['getReportsCount']({ reports, status: 'archived' }); + expect(countDraft).toBe(2); + expect(countLive).toBe(1); + expect(countArchived).toBe(1); + }); + + it('should return 0 when reports array does not contain elements with specified status', () => { + const reports = [ + { id: 1, status: 'draft' }, + { id: 2, status: 'draft' }, + { id: 3, status: 'draft' } + ]; + const count = component['getReportsCount']({ reports, status: 'live' }); + expect(count).toBe(0); + }); + + it('should return correct count when status is not specified', () => { + const reports = [ + { id: 1, status: 'draft' }, + ]; + const count = component['getReportsCount']({ reports }); + expect(count).toBe(reports.length); + }); + + it('should prepare table when inputTag is set', () => { + const mockElementRef: ElementRef = { + nativeElement: document.createElement('div') + }; + const mockReports = [{ id: 1, name: 'Report 1' }, { id: 2, name: 'Report 2' }]; + component.reports = [{ id: 1, name: "Report 1" }] + component.prepareTable = jest.fn(); + component.inputTag = mockElementRef; + expect(component.prepareTable).toHaveBeenCalledWith(mockElementRef.nativeElement, mockReports[0]); + }); + + + it('should not prepare table when inputTag is null', () => { + component.prepareTable = jest.fn(); + component.inputTag = null; + expect(component.prepareTable).not.toHaveBeenCalled(); + }); + + it('should not prepare dataset table when datasetTable is null', () => { + mockReportService.isUserReportAdmin = jest.fn().mockReturnValue(false); + component.prepareTable = jest.fn(); + component.datasetTable = null; + expect(component.prepareTable).not.toHaveBeenCalled(); + }); + + it('should render tags correctly when data is an array', () => { + // Arrange + const data = ['tag1', 'tag2', 'tag3']; + // Act + const renderedTags = component['renderTags'](data); + // Assert + const expectedHtml = `
Tag 1 Tag 2 Tag 3
` + expect(renderedTags).toEqual(expectedHtml); + }); + + it('should render single tag correctly when data is not an array', () => { + // Arrange + const data = 'tag'; + // Act + const renderedTags = component['renderTags'](data); + // Assert + const expectedHtml = 'Tag'; + expect(renderedTags).toEqual(expectedHtml); + }); + + it('should return telemetry interact data with default id when only type is provided', () => { + // Arrange + const type = 'some-type'; + // Act + const telemetryData = component.setTelemetryInteractEdata({ type }); + // Assert + expect(telemetryData).toEqual({ + id: 'reports-list', + type, + pageid: 'test-pageid' + }); + }); + + it('should return telemetry interact data with provided id and type', () => { + // Arrange + const type = 'another-type'; + const id = 'custom-id'; + // Act + const telemetryData = component.setTelemetryInteractEdata({ type, id }); + // Assert + expect(telemetryData).toEqual({ + id, + type, + pageid: 'test-pageid' + }); + }); + + + it('should log telemetry correctly', () => { + // Arrange + const type = 'testType'; + const cdata = [{ id: 'testId', type: 'testType' }]; + const id = 'testId'; + // Act + component['logTelemetry']({ type, cdata, id }); + // Assert + expect(mockTelemetryService.interact).toHaveBeenCalledWith({ + context: { + env: 'test-env', + cdata + }, + edata: Object({ + id: 'reports-list', + type: 'testType', + pageid: 'test-pageid' + }), + object: Object({ + type: 'Report', + ver: "1.0", + id: "testId", + rollup: Object({}), + }) + }); + }); + it('should return materialized child rows if user is super admin', () => { + // Arrange + mockReportService.isUserSuperAdmin = jest.fn().mockReturnValue(true); + const reports = [{ id: 1 }, { id: 2 }]; + // Act + const result = component['filterReportsBasedOnRoles'](reports); + // Assert + expect(result).toEqual(mockReportService.getMaterializedChildRows(reports)); + }); + +}); \ No newline at end of file diff --git a/src/app/client/src/app/modules/dashboard/components/list-all-reports/list-all-reports.component.ts b/src/app/client/src/app/modules/dashboard/components/list-all-reports/list-all-reports.component.ts index 4bcf22b7b64..7c9c2f7d158 100644 --- a/src/app/client/src/app/modules/dashboard/components/list-all-reports/list-all-reports.component.ts +++ b/src/app/client/src/app/modules/dashboard/components/list-all-reports/list-all-reports.component.ts @@ -252,7 +252,7 @@ export class ListAllReportsComponent implements OnInit { $(el).on('click', 'tbody tr td:not(.details-control)', (event) => { const rowData = masterTable && masterTable.row(event?.currentTarget).data(); - if (_.get(rowData, 'reportid') && _.get(rowData, 'hashed_val') && rowData.hasOwnProperty('materialize')) { + if (_.get(rowData, 'reportid') && _.get(rowData, 'hashed_val') || (this.reportService.isUserSuperAdmin() && rowData?.hasOwnProperty('materialize'))) { const reportid = _.get(rowData,'reportid'); const hashed_val = _.get(rowData,'hashed_val'); const materialize = _.get(rowData,'materialize'); diff --git a/src/app/client/src/app/modules/dial-code-search/components/get/get.component.spec.ts b/src/app/client/src/app/modules/dial-code-search/components/get/get.component.spec.ts new file mode 100644 index 00000000000..56efca96e9b --- /dev/null +++ b/src/app/client/src/app/modules/dial-code-search/components/get/get.component.spec.ts @@ -0,0 +1,109 @@ + +/** +* Description. +* This spec file was created using ng-test-barrel plugin! +* +*/ + +import { ResourceService, NavigationHelperService, LayoutService } from '@sunbird/shared'; +import { Router, ActivatedRoute } from '@angular/router'; +import { _ } from 'lodash-es'; +import { GetComponent } from './get.component'; +import { of } from 'rxjs'; + +describe('GetComponent', () => { + let component: GetComponent; + + const resourceService: Partial = {}; + const router: Partial = { + navigate: jest.fn() + + }; + const activatedRoute: Partial = { + queryParams: of({ + type: 'edit', + courseId: 'do_456789', + batchId: '124631256' + }), + params: of({ collectionId: "123" }), + snapshot: { + data: { + telemetry: { + env: 'certs', + pageid: 'certificate-configuration', + type: 'view', + subtype: 'paginate', + ver: '1.0' + } + } + } as any + }; + const navigationhelperService: Partial = { + getPageLoadTime :jest.fn() + }; + const layoutService: Partial = { + initlayoutConfig: jest.fn(), + switchableLayout: jest.fn(), + + }; + + beforeAll(() => { + component = new GetComponent( + resourceService as ResourceService, + router as Router, + activatedRoute as ActivatedRoute, + navigationhelperService as NavigationHelperService, + layoutService as LayoutService + ) + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + + it('should call ngOnInit', () => { + Object.defineProperty(window, 'EkTelemetry', { value: { config: {} } }); + layoutService.switchableLayout = jest.fn(() => of([{ data: '' }])); + component.ngOnInit() + expect(layoutService.switchableLayout).toHaveBeenCalled(); + }); + + it('should set values on ngAfterViewInit',(done) =>{ + component.ngAfterViewInit(); + setTimeout(() => { + expect(component.telemetryImpression).toEqual({ + context: { + env: activatedRoute.snapshot.data.telemetry.env + }, + edata: { + type: activatedRoute.snapshot.data.telemetry.type, + pageid: activatedRoute.snapshot.data.telemetry.pageid, + uri: activatedRoute.snapshot.data.telemetry.uri, + duration: navigationhelperService.getPageLoadTime() + } + }); + done() + }); + }); + + it('should call ngOnInit', () => { + component.searchKeyword='test' + component.navigateToSearch() + expect(router.navigate).toHaveBeenCalled(); + + }); + + it('should unsubscribe from subscriptions on destroy', () => { + const mockResourceDataSubscription = { + unsubscribe: jest.fn(), + }; + component.unsubscribe$ = { + next: jest.fn(), + complete: jest.fn() + } as any; + + component.ngOnDestroy(); + expect(component.unsubscribe$.next).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/src/app/client/src/app/modules/explore-page/components/explore-page/explore-page.component.html b/src/app/client/src/app/modules/explore-page/components/explore-page/explore-page.component.html index 5e14735e46b..8f0ab0294dc 100644 --- a/src/app/client/src/app/modules/explore-page/components/explore-page/explore-page.component.html +++ b/src/app/client/src/app/modules/explore-page/components/explore-page/explore-page.component.html @@ -47,7 +47,7 @@

-

{{resourceService?.frmelmnts?.lbl?.hi}} {{userProfile?.firstName +

{{resourceService?.frmelmnts?.lbl?.hi}} {{userProfile?.firstName | titlecase}} {{userProfile?.lastName | titlecase}}

{{resourceService?.frmelmnts?.lbl?.yourPreferences}} @@ -57,8 +57,8 @@

{{ (resourceService?.frmelmnts?.lbl[category?.code] | transposeTerms: resourceService?.frmelmnts?.lbl[category?.code] : resourceService?.selectedLang) || category?.labels }}: - {{category?.values[0]}} ...+ {{category?.values?.length-1}} - {{category?.values[0]}} + {{category?.values[0]}} ...+ {{category?.values?.length-1}} + {{category?.values[0]}}

@@ -81,7 +81,7 @@

-
{{getBannerTitle(section?.section?.title)}} + diff --git a/src/app/client/src/app/modules/explore-page/components/explore-page/explore-page.component.scss b/src/app/client/src/app/modules/explore-page/components/explore-page/explore-page.component.scss index ff32d69c35a..cd195a418a8 100644 --- a/src/app/client/src/app/modules/explore-page/components/explore-page/explore-page.component.scss +++ b/src/app/client/src/app/modules/explore-page/components/explore-page/explore-page.component.scss @@ -23,6 +23,11 @@ flex: 1; .header { font-size: calculateRem(16px); + font-weight: 500; + } + + .value { + font-weight: 500; } .preference-icon{ position: relative; @@ -62,6 +67,9 @@ display: block !important; } } +.featured-text { + font-weight: 500; +} .home-container { padding:0; } diff --git a/src/app/client/src/app/modules/groups/components/activity/add-activity-content-types/add-activity-content-types.component.spec.ts b/src/app/client/src/app/modules/groups/components/activity/add-activity-content-types/add-activity-content-types.component.spec.ts index 0fd79a9e65d..171a3e1bd23 100644 --- a/src/app/client/src/app/modules/groups/components/activity/add-activity-content-types/add-activity-content-types.component.spec.ts +++ b/src/app/client/src/app/modules/groups/components/activity/add-activity-content-types/add-activity-content-types.component.spec.ts @@ -35,6 +35,9 @@ describe('AddActivityContentTypesComponent', () => { } } } as any, + queryParams: of({groupName:'test', + createdBy:'test'}), + }; const mockRouter :Partial ={ url: '/mock-url', @@ -75,6 +78,13 @@ describe('AddActivityContentTypesComponent', () => { expect(component).toBeTruthy(); }); + it('should call ngOnInit', () => { + mockLayoutService.switchableLayout = jest.fn(() => of([{ data: '' }])); + mockGroupService.getSupportedActivityList = jest.fn(() => of([{ data: '' }])); + component.ngOnInit() + expect(mockLayoutService.switchableLayout).toHaveBeenCalled(); + }); + it('should destroy activity-content', () => { component.unsubscribe$ = { next: jest.fn(), diff --git a/src/app/client/src/app/modules/groups/services/groups/groups.service.spec.ts b/src/app/client/src/app/modules/groups/services/groups/groups.service.spec.ts new file mode 100644 index 00000000000..d4875e6f401 --- /dev/null +++ b/src/app/client/src/app/modules/groups/services/groups/groups.service.spec.ts @@ -0,0 +1,628 @@ +import { Router } from '@angular/router'; +import { CsGroupAddActivitiesRequest, CsGroupRemoveActivitiesRequest, CsGroupSearchCriteria, CsGroupUpdateActivitiesRequest, CsGroupUpdateMembersRequest, CsGroupUpdateGroupGuidelinesRequest, CsGroupSupportedActivitiesFormField } from '@project-sunbird/client-services/services/group/interface'; +import { UserService, LearnerService, TncService } from '@sunbird/core'; +import { NavigationHelperService, ResourceService, ConfigService } from '@sunbird/shared'; +import { TelemetryService } from '@sunbird/telemetry'; +import { _ } from 'lodash-es'; +import { IGroupCard, IGroupUpdate, IMember, MY_GROUPS } from '../../interfaces'; +import { CsLibInitializerService } from './../../../../service/CsLibInitializer/cs-lib-initializer.service'; +import { CsGroup } from '@project-sunbird/client-services/models'; +import { GroupsService } from './groups.service'; +jest.mock('@project-sunbird/client-services', () => { + return { + CsModule: { + instance: { + config: { + core: { + api: { + authentication: { + userToken: '' + } + } + } + }, + groupService: { + updateById: jest.fn(), + create: jest.fn(), + search: jest.fn(), + getById: jest.fn(), + deleteById: jest.fn(), + addMembers: jest.fn(), + updateMembers: jest.fn(), + removeMembers: jest.fn(), + addActivities: jest.fn(), + updateActivities: jest.fn(), + removeActivities: jest.fn(), + suspendById: jest.fn(), + reactivateById: jest.fn(), + updateGroupGuidelines: jest.fn(), + + activityService: { + getDataAggregation: jest.fn(), + getDataForDashlets: jest.fn() + } + }, + userService: { + checkUserExists: jest.fn(), + userProfile: { + + } + + }, + updateConfig: jest.fn().mockImplementation(() => { + }) + }, + updateById: {} + } + }; +}); +describe('GroupsService', () => { + let service: GroupsService; + const csLibInitializerService: Partial = { + initializeCs: jest.fn() + }; + const userService: Partial = { + userid: 'mocked-userid', + userProfile: { + _userData: { + allTncAccepted: { + groupsTnc: { + version: 1 + } + } + } + + }, + }; + const resourceService: Partial = { + languageSelected$: { + pipe: jest.fn().mockReturnValueOnce({ + subscribe: jest.fn((callback: any) => { + setTimeout(() => { + callback({ value: 'en' }); + }, 600); + }) + }) + } as any, + frmelmnts: { + lbl: { + you: 'You' + } + } + }; + const telemetryService: Partial = { + interact: jest.fn() + }; + const navigationhelperService: Partial = { + goBack: jest.fn(), + getPageLoadTime: jest.fn() + }; + const router: Partial = { + navigate: jest.fn() + }; + const configService: Partial = { + urlConFig: { + URLS: { + SYSTEM_SETTING: { + GOOGLE_RECAPTCHA: 'mocked-url' + } + } + }, + }; + const learnerService: Partial = { + + get: jest.fn() + }; + const tncService: Partial = {}; + beforeAll(() => { + service = new GroupsService( + csLibInitializerService as CsLibInitializerService, + userService as UserService, + resourceService as ResourceService, + telemetryService as TelemetryService, + navigationhelperService as NavigationHelperService, + router as Router, + configService as ConfigService, + learnerService as LearnerService, + tncService as TncService + + ) + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + + }); + it('should create a instance of service', () => { + expect(service).toBeTruthy(); + }); + + it('should set current user role correctly', () => { + const members = [ + { userId: '1', role: 'admin' }, + { userId: '2', role: 'member' }, + ]; + service['userService'] = { userid: '2' } as any + service['groupData'] = { createdBy: '1' } as any; + service.setCurrentUserRole(members); + expect(service.isCurrentUserAdmin).toBe(false); + expect(service.isCurrentUserCreator).toBe(false); + }); + + it('should assign random colors to each group in the provided groupList array', () => { + const groupList = [ + { id: 1, name: 'Group 1' }, + { id: 2, name: 'Group 2' }, + { id: 3, name: 'Group 3' } + ] as any + + const result = service.addGroupPaletteList(groupList); + + expect(result).toHaveLength(groupList.length); + result.forEach(group => { + expect(group).toHaveProperty('cardBgColor'); + expect(group).toHaveProperty('cardTitleColor'); + }); + }); + + it('should return an empty array if groupList is null', () => { + const result = service.addGroupPaletteList(null); + + expect(result).toEqual([]); + }); + + it('should call learnerService.get with the constructed systemSetting object', () => { + service.getRecaptchaSettings(); + + expect(learnerService.get).toHaveBeenCalledWith({ + url: configService.urlConFig.URLS.SYSTEM_SETTING.GOOGLE_RECAPTCHA + }); + }); + + it('should call groupCservice.updateById with the provided groupId and updateRequest', () => { + service['groupCservice'].updateById = jest.fn() + const groupId = 'group1'; + const updateRequest: IGroupUpdate = { + } as any + service.updateGroup(groupId, updateRequest); + expect(service['groupCservice'].updateById).toHaveBeenCalledWith(groupId, updateRequest); + }); + it('should call groupCservice.create with the provided groupData', () => { + service['groupCservice'].create = jest.fn() + const groupData: IGroupCard = { + } as any; + + service.createGroup(groupData); + + expect(service['groupCservice'].create).toHaveBeenCalledWith(groupData); + }); + + it('should call groupCservice.search with the provided search request', () => { + service['groupCservice'].search = jest.fn() + const searchRequest: CsGroupSearchCriteria = { + } as any; + service.searchUserGroups(searchRequest); + expect(service['groupCservice'].search).toHaveBeenCalledWith(searchRequest); + }); + + it('should call groupCservice.getById with the provided groupId and options', () => { + service['groupCservice'].getById = jest.fn() + const groupId = 'group1'; + const includeMembers = true; + const includeActivities = false; + const groupActivities = true; + service.getGroupById(groupId, includeMembers, includeActivities, groupActivities); + expect(service['groupCservice'].getById).toHaveBeenCalledWith(groupId, { + includeMembers, + includeActivities, + groupActivities + }); + }); + + it('should add fields to the member object based on the defined logic', () => { + const member = { + name: 'John', + userName: 'john_doe', + userId: 'user123', + identifier: 'identifier123', + role: 'admin', + isSelf: 'pkg', + isMenu: "test" + }; + service.isCurrentUserCreator = true + const result = service.addFields(member); + + expect(result.title).toBe('John'); + expect(result.initial).toBe('J'); + expect(result.identifier).toBe('user123'); + expect(result.isAdmin).toBe(true); + expect(result.isCreator).toBe(false); + expect(result.isMenu).toBe(true); + }); + it('should call groupCservice.deleteById with the provided groupId', () => { + service['groupCservice'].deleteById = jest.fn() + service.deleteGroupById('groupId123'); + expect(service['groupCservice'].deleteById).toHaveBeenCalledWith('groupId123'); + }); + + it('should call groupCservice.addMembers with the provided groupId and members', () => { + service['groupCservice'].addMembers = jest.fn(); + const members: IMember = { + } as any; + service.addMemberById('groupId123', members); + expect(service['groupCservice'].addMembers).toHaveBeenCalledWith('groupId123', members); + }); + + it('should call groupCservice.updateMembers with the provided groupId and updateMembersRequest', () => { + service['groupCservice'].updateMembers = jest.fn(); + const updateMembersRequest: CsGroupUpdateMembersRequest = { + } as any; + service.updateMembers('groupId123', updateMembersRequest); + expect(service['groupCservice'].updateMembers).toHaveBeenCalledWith('groupId123', updateMembersRequest); + }); + + it('should call groupCservice.removeMembers with the provided groupId and userIds', () => { + service['groupCservice'].removeMembers = jest.fn() + const userIds: string[] = ['user1', 'user2']; + service.removeMembers('groupId123', userIds); + expect(service['groupCservice'].removeMembers).toHaveBeenCalledWith('groupId123', { userIds }); + }); + + it('should call groupCservice.addActivities with the provided groupId and addActivitiesRequest', () => { + service['groupCservice'].addActivities = jest.fn(); + const addActivitiesRequest: CsGroupAddActivitiesRequest = { + } as any; + + service.addActivities('groupId123', addActivitiesRequest); + + expect(service['groupCservice'].addActivities).toHaveBeenCalledWith('groupId123', addActivitiesRequest); + }); + + it('should call groupCservice.updateActivities with the provided groupId and updateActivitiesRequest', () => { + service['groupCservice'].updateActivities = jest.fn(); + const updateActivitiesRequest: CsGroupUpdateActivitiesRequest = { + activities: { + id: 'string', + type: 'test', + status: { + ACTIVE: "active", + INACTIVE: "inactive", + SUSPENDED: "suspended" + } + } as any + }; + + service.updateActivities('groupId123', updateActivitiesRequest); + + expect(service['groupCservice'].updateActivities).toHaveBeenCalledWith('groupId123', updateActivitiesRequest); + }); + + it('should call groupCservice.removeActivities with the provided groupId and removeActivitiesRequest', () => { + service['groupCservice'].removeActivities = jest.fn(); + const removeActivitiesRequest: CsGroupRemoveActivitiesRequest = { + activityIds: ['test'] + }; + + service.removeActivities('groupId123', removeActivitiesRequest); + + expect(service['groupCservice'].removeActivities).toHaveBeenCalledWith('groupId123', removeActivitiesRequest); + }); + + it('should call userCservice.checkUserExists with the provided memberId and captchaToken', () => { + service['userCservice'].checkUserExists = jest.fn(); + const captchaToken = {}; + service.getUserData('memberId123', captchaToken); + expect(service['userCservice'].checkUserExists).toHaveBeenCalledWith({ key: 'userName', value: 'memberId123' }, captchaToken); + }); + + it('should call groupCservice.activityService.getDataAggregation with the provided groupId, activity, mergeGroup, and leafNodesCount', () => { + service['groupCservice'].activityService.getDataAggregation = jest.fn(); + const groupId = 'groupId123'; + const activity = 'activity123'; + const mergeGroup = true; + const leafNodesCount = 5; + service.getActivity(groupId, activity, mergeGroup, leafNodesCount); + expect(service['groupCservice'].activityService.getDataAggregation).toHaveBeenCalledWith(groupId, activity, mergeGroup, leafNodesCount); + }); + + it('should call telemetryService.interact with the correct interactData object', () => { + const eid = { + id: 'eventId123', + edata: { + type: 'eventType', + subtype: 'eventSubtype' + }, + extra: { + key1: 'value1', + key2: 'value2' + } + }; + const routeData = { + params: { + groupId: 'groupId123' + }, + data: { + telemetry: { + env: 'testEnvironment', + pageid: 'testPageId' + } + } + }; + const cdata = []; + const groupId = 'groupId123'; + const obj = { + key: 'value' + }; + service.addTelemetry(eid, routeData, cdata, groupId, obj); + expect(telemetryService.interact).toHaveBeenCalledWith({ + context: { + env: 'testEnvironment', + cdata: [{ id: 'groupId123', type: 'Group' }] + }, + edata: { + id: 'eventId123', + type: 'eventType', + subtype: 'eventSubtype', + extra: { + key1: 'value1', + key2: 'value2' + }, + pageid: 'testPageId' + }, + object: { + key: 'value' + } + }); + }); + + it('should handle empty activitiesGrouped', () => { + const showList = false; + const groupData = {}; + + const result = service.groupContentsByActivityType(showList, groupData); + + expect(result.showList).toBe(false); + expect(result.activities).toEqual({}); + }); + + it('should emit showActivateModal event with the provided name and eventName', () => { + const name = 'testName'; + const eventName = 'testEvent'; + jest.spyOn(service.showActivateModal, 'emit') + service.emitActivateEvent(name, eventName); + expect(service.showActivateModal.emit).toHaveBeenCalledWith({ name, eventName }); + }); + + it('should call groupCservice.suspendById with the provided groupId', () => { + service['groupCservice'].suspendById = jest.fn(); + const groupId = 'groupId123'; + service.deActivateGroupById(groupId); + expect(service['groupCservice'].suspendById).toHaveBeenCalledWith(groupId); + }); + + it('should call groupCservice.reactivateById with the provided groupId', () => { + service['groupCservice'].reactivateById = jest.fn() + const groupId = 'groupId123'; + service.activateGroupById(groupId); + expect(service['groupCservice'].reactivateById).toHaveBeenCalledWith(groupId); + }); + + it('should emit updateEvent with the provided value', () => { + const value = 'testValue'; + jest.spyOn(service.updateEvent, 'emit'); + service.emitUpdateEvent(value); + expect(service.updateEvent.emit).toHaveBeenCalledWith(value); + }); + + it('should update the group status and return whether the group is active', () => { + const group = new CsGroup(); + const status = + { + ACTIVE: "active", + } as any; + const isActive = service.updateGroupStatus(group, status); + expect(group.status).toBe(status); + expect(isActive).toBe(group.isActive()); + }); + + + it('should emit the showMenu event with the provided visibility parameter', () => { + const visibility = true; + jest.spyOn(service.showMenu, 'emit') + service.emitMenuVisibility(visibility); + expect(service.showMenu.emit).toHaveBeenCalledWith(visibility); + }); + it('should emit the closeForm event', () => { + jest.spyOn(service.closeForm, 'emit') + service.emitCloseForm(); + expect(service.closeForm.emit).toHaveBeenCalled(); + }); + + it('should emit the membersList event with the provided members', () => { + const members = [{}, {}] as any; + jest.spyOn(service.membersList, 'emit') + service.emitMembers(members); + expect(service.membersList.emit).toHaveBeenCalledWith(members); + }); + + it('should emit the showLoader event with the provided value', () => { + const value = true; + jest.spyOn(service.showLoader, 'emit') + service.emitShowLoader(value); + expect(service.showLoader.emit).toHaveBeenCalledWith(value); + }); + + + it('should construct the impression object with default edata properties when edata is not provided', () => { + const routeData = { + data: { + telemetry: { + type: 'defaultType', + subtype: 'defaultSubtype', + env: 'defaultEnv', + pageid: 'defaultPageId', + } + }, + params: {} + }; + const url = 'testUrl'; + + const impressionObj = service.getImpressionObject(routeData, url); + + expect(impressionObj).toEqual({ + context: { + env: 'defaultEnv' + }, + edata: { + type: 'defaultType', + pageid: 'defaultPageId', + subtype: 'defaultSubtype', + uri: 'testUrl', + } + }); + }); + it('should include object information in the impression object if groupId is present in routeData', () => { + const routeData = { + data: { + telemetry: { + type: 'defaultType', + subtype: 'defaultSubtype', + env: 'defaultEnv', + pageid: 'defaultPageId' + } + }, + params: { + groupId: 'testGroupId' + } + }; + const url = 'testUrl'; + const impressionObj = service.getImpressionObject(routeData, url); + expect(impressionObj).toEqual({ + context: { + env: 'defaultEnv' + }, + edata: { + type: 'defaultType', + pageid: 'defaultPageId', + subtype: 'defaultSubtype', + uri: 'testUrl', + }, + object: { + id: 'testGroupId', + type: 'Group', + ver: '1.0' + } + }); + }); + + + it('should return false if the groups TNC version is equal to or greater than the latest version', () => { + + const result = service.isTncUpdated(); + + expect(result).toBe(false); + }); + it('should return an empty array if members array is falsy', () => { + const result = service.addFieldsToMember(null); + expect(result).toEqual([]); + }); + + it('should correctly add fields to each member and sort the array based on criteria', () => { + const members = [ + { userId: 1, name: 'John', role: 'admin' }, + { userId: 2, name: 'Alice', role: 'member' } + ]; + + const result = service.addFieldsToMember(members); + + expect(result).toEqual([ + { userId: 1, name: 'John', role: 'admin', indexOfMember: 0, isAdmin: true, isSelf: false, isMenu: false, title: 'John', initial: 'J', identifier: 1, isCreator: false, }, + { userId: 2, name: 'Alice', role: 'member', indexOfMember: 1, isAdmin: false, isSelf: false, isMenu: false, title: 'Alice', initial: 'A', identifier: 2, isCreator: false, } + ]); + }); + it('should call getDataForDashlets method of activityService with the provided data', () => { + service['groupCservice'].activityService.getDataForDashlets = jest.fn() + const courseHeirarchyData = {}; + const aggData = {}; + service.getDashletData(courseHeirarchyData, aggData); + expect(service['groupCservice'].activityService.getDataForDashlets).toHaveBeenCalledWith(courseHeirarchyData, aggData); + }); + it('should call updateGroupGuidelines method of groupCService with the provided request', () => { + service['groupCservice'].updateGroupGuidelines = jest.fn(); + const request = {} as any; + service.updateGroupGuidelines(request); + expect(service['groupCservice'].updateGroupGuidelines).toHaveBeenCalledWith(request); + }); + + it('should navigate to MY_GROUPS if _history has only one item', () => { + navigationhelperService['_history'] = ['someRoute']; + service.goBack(); + expect(router.navigate).toHaveBeenCalledWith([MY_GROUPS]); + }); + it('should call navigationhelperService.goBack if _history has more than one item', () => { + navigationhelperService['_history'] = ['someRoute1', 'someRoute2']; + + service.goBack(); + + expect(navigationhelperService.goBack).toHaveBeenCalled(); + }); + it('should return the latestTnc value when using the getter method', () => { + const groupsTnc = {}; + service['_groupsTnc'] = groupsTnc; + const result = service.latestTnc; + expect(result).toEqual(groupsTnc); + }); + + it('should set the userData value when using the setter method', () => { + const userData = {}; + service.userData = userData; + expect(service['_userData']).toEqual(userData); + }); + it('should return true if user has accepted TNC and groupsTnc is not empty', () => { + service['_userData'] = { + allTncAccepted: { + groupsTnc: { + version: 1 + } + } + }; + const result = service.isUserAcceptedTnc(); + expect(result).toBe(true); + }); + + it('should return false if user has not accepted TNC or groupsTnc is empty', () => { + service['_userData'] = { + allTncAccepted: { + groupsTnc: { + + } + } + }; + const resultEmptyGroupsTnc = service.isUserAcceptedTnc(); + + expect(resultEmptyGroupsTnc).toBe(false); + + service['_userData'] = { + allTncAccepted: {} + }; + const resultNoGroupsTnc = service.isUserAcceptedTnc(); + expect(resultNoGroupsTnc).toBe(false); + }); + + it('should parse groupsTnc value if it is a string before setting', () => { + const groupsTnc = { + value: '{"key": "value"}' + }; + service.groupsTncDetails = groupsTnc; + expect(service['_groupsTnc']).toEqual({ value: { key: 'value' } }); + }); + + it('should set groupsTnc value as is if it is not a string', () => { + const groupsTnc = { + value: { key: 'value' } + }; + service.groupsTncDetails = groupsTnc; + expect(service['_groupsTnc']).toEqual(groupsTnc); + }); +}); \ No newline at end of file diff --git a/src/app/client/src/app/modules/learn/components/batch/create-batch/create-batch.component.html b/src/app/client/src/app/modules/learn/components/batch/create-batch/create-batch.component.html index 01f38a439d7..7020ed904d7 100644 --- a/src/app/client/src/app/modules/learn/components/batch/create-batch/create-batch.component.html +++ b/src/app/client/src/app/modules/learn/components/batch/create-batch/create-batch.component.html @@ -95,7 +95,6 @@
- {{resourceService?.frmelmnts?.lbl?.startdate}} @@ -110,7 +109,6 @@
- {{resourceService?.frmelmnts?.lbl?.enddate}} @@ -124,7 +122,6 @@
- {{resourceService?.frmelmnts?.lbl?.enrollmentenddate}} diff --git a/src/app/client/src/app/modules/learn/components/course-consumption/course-consumption-header/course-consumption-header.component.spec.ts b/src/app/client/src/app/modules/learn/components/course-consumption/course-consumption-header/course-consumption-header.component.spec.ts index 7be892ed5d2..36caca70b99 100644 --- a/src/app/client/src/app/modules/learn/components/course-consumption/course-consumption-header/course-consumption-header.component.spec.ts +++ b/src/app/client/src/app/modules/learn/components/course-consumption/course-consumption-header/course-consumption-header.component.spec.ts @@ -1,11 +1,11 @@ import { takeUntil } from 'rxjs/operators'; -import { Component,OnInit,Input,AfterViewInit,ChangeDetectorRef,OnDestroy } from '@angular/core'; -import { CourseConsumptionService,CourseProgressService } from './../../../services'; -import { ActivatedRoute,Router } from '@angular/router'; +import { Component, OnInit, Input, AfterViewInit, ChangeDetectorRef, OnDestroy } from '@angular/core'; +import { CourseConsumptionService, CourseProgressService } from './../../../services'; +import { ActivatedRoute, Router } from '@angular/router'; import { _ } from 'lodash-es'; -import { CoursesService,PermissionService,CopyContentService,OrgDetailsService,UserService,GeneraliseLabelService } from '@sunbird/core'; -import { ResourceService,ToasterService,ContentData,ContentUtilsServiceService,ITelemetryShare,ExternalUrlPreviewService,UtilService,ConnectionService,OfflineCardService,ServerResponse } from '@sunbird/shared'; -import { IInteractEventObject,TelemetryService } from '@sunbird/telemetry'; +import { CoursesService, PermissionService, CopyContentService, OrgDetailsService, UserService, GeneraliseLabelService } from '@sunbird/core'; +import { ResourceService, ToasterService, ContentData, ContentUtilsServiceService, ITelemetryShare, ExternalUrlPreviewService, UtilService, ConnectionService, OfflineCardService, ServerResponse } from '@sunbird/shared'; +import { IInteractEventObject, TelemetryService } from '@sunbird/telemetry'; import dayjs from 'dayjs'; import { GroupsService } from '../../../../groups/services/groups/groups.service'; import { NavigationHelperService } from '@sunbird/shared'; @@ -17,91 +17,138 @@ import { ContentManagerService } from '../../../../public/module/offline/service import { DiscussionTelemetryService } from './../../../../shared/services/discussion-telemetry/discussion-telemetry.service'; import { CourseConsumptionHeaderComponent } from './course-consumption-header.component'; import { of, throwError } from 'rxjs'; +import { MockResponseData } from './course-consumption-header.spec.data'; +import { json } from 'body-parser'; describe('CourseConsumptionHeaderComponent', () => { - let component: CourseConsumptionHeaderComponent; + let component: CourseConsumptionHeaderComponent; - const mockActivatedRoute :Partial ={ - snapshot:{ - params:{ - courseId: 'mock-course-id', + const mockActivatedRoute: Partial = { + snapshot: { + params: { + courseId: 'mock-course-id', }, - data:{ - telemetry:{ + data: { + telemetry: { env: 'mock-env', pageid: 'page-id', } } } as any, }; - const mockCourseConsumptionService :Partial ={ + const mockCourseConsumptionService: Partial = { getContentRollUp: jest.fn(), getRollUp: jest.fn(), + setCoursePagePreviousUrl: jest.fn(), + isTrackableCollection: jest.fn(), + canViewDashboard: jest.fn() }; - const mockResourceService :Partial ={ + const mockResourceService: Partial = { messages: { - fmsg:{ + fmsg: { m0090: 'mock-error-message', + m0004: 'Could not fetch data, try again later' }, stmsg: { - desktop: { - deleteCourseSuccessMessage: 'Course deleted successfully' - } + desktop: { + deleteCourseSuccessMessage: 'Course deleted successfully' + }, + m0140: 'Downloading' }, etmsg: { - desktop: { - deleteCourseErrorMessage: 'Error deleting course' - } + desktop: { + deleteCourseErrorMessage: 'Error deleting course' + } }, - smsg:{ - m0059: 'mock-error-message' + smsg: { + m0059: 'mock-error-message', + m0056: 'You should be online to update the {contentName}' }, } - + }; - const mockRouter :Partial ={ + const mockRouter: Partial = { navigate: jest.fn(), }; - const mockPermissionService :Partial ={}; - const mockToasterService :Partial ={ + const mockPermissionService: Partial = { + checkRolesPermissions: jest.fn(() => { + return true; + }) as any + }; + const mockToasterService: Partial = { success: jest.fn(), - error: jest.fn(), + error: jest.fn(), + }; + const mockCopyContentService: Partial = {}; + const mockChangeDetectorRef: Partial = {}; + const mockCourseProgressService: Partial = {}; + const mockContentUtilsServiceService: Partial = {}; + const mockExternalUrlPreviewService: Partial = {}; + const mockCoursesService: Partial = {}; + const mockUserService: Partial = { + loggedIn: true, + slug: jest.fn().mockReturnValue('tn') as any, + userData$: of({ + userProfile: { + userId: 'sample-uid', + rootOrgId: 'sample-root-id', + rootOrg: { + rootOrgId: 'sample-root-id' + }, + hashTagIds: ['id'] + } as any + }) as any, + setIsCustodianUser: jest.fn(), + setGuestUser: jest.fn(), + userid: 'sample-uid', + userProfile: { + rootOrg: { + rootOrgId: 'sample-root-id' + } + }, + appId: 'sample-id', + getServerTimeDiff: '', }; - const mockCopyContentService :Partial ={}; - const mockChangeDetectorRef :Partial ={}; - const mockCourseProgressService :Partial ={}; - const mockContentUtilsServiceService :Partial ={}; - const mockExternalUrlPreviewService :Partial ={}; - const mockCoursesService :Partial ={}; - const mockUserService :Partial ={}; - const mockTelemetryService :Partial ={ + const mockTelemetryService: Partial = { interact: jest.fn(), }; - const mockGroupService :Partial ={}; - const mockNavigationHelperService :Partial ={}; - const mockOrgDetailsService :Partial ={}; - const mockGeneraliseLabelService :Partial ={}; - const mockConnectionService :Partial ={}; - const mockCourseBatchService :Partial ={}; - const mockUtilService :Partial ={}; - const mockContentManagerService :Partial ={ + const mockGroupService: Partial = {}; + const mockNavigationHelperService: Partial = {}; + const mockOrgDetailsService: Partial = { + + }; + const mockGeneraliseLabelService: Partial = {}; + const mockConnectionService: Partial = { + monitor: jest.fn(() => of(true)) + }; + const mockCourseBatchService: Partial = { + getAllBatchDetails: jest.fn(), + }; + const mockUtilService: Partial = { + getPlayerDownloadStatus: jest.fn(), + //isDesktopApp:true + }; + const mockContentManagerService: Partial = { downloadContentId: '', - downloadContentData: {}, - failedContentName: '', - startDownload: jest.fn(), - deleteContent: jest.fn(), + downloadContentData: {}, + failedContentName: '', + startDownload: jest.fn(), + deleteContent: jest.fn(), exportContent: jest.fn(), + updateContent: jest.fn(), + }; + const mockFormService: Partial = { + getFormConfig: jest.fn(), }; - const mockFormService :Partial ={}; - const mockOfflineCardService :Partial ={ + const mockOfflineCardService: Partial = { isYoutubeContent: jest.fn(), }; - const mockDiscussionService :Partial ={}; - const mockDiscussionTelemetryService :Partial ={}; + const mockDiscussionService: Partial = {}; + const mockDiscussionTelemetryService: Partial = {}; - beforeAll(() => { - component = new CourseConsumptionHeaderComponent( - mockActivatedRoute as ActivatedRoute, + beforeAll(() => { + component = new CourseConsumptionHeaderComponent( + mockActivatedRoute as ActivatedRoute, mockCourseConsumptionService as CourseConsumptionService, mockResourceService as ResourceService, mockRouter as Router, @@ -127,27 +174,27 @@ describe('CourseConsumptionHeaderComponent', () => { mockOfflineCardService as OfflineCardService, mockDiscussionService as DiscussionService, mockDiscussionTelemetryService as DiscussionTelemetryService - ) - }); + ) + }); - beforeEach(() => { + beforeEach(() => { jest.mock('dayjs', () => jest.fn(date => ({ isBefore: jest.fn(() => true) // Mocking always true for simplicity }))); - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - it('should create a instance of component', () => { - expect(component).toBeTruthy(); - }); + jest.clearAllMocks(); + jest.resetAllMocks(); + }); - it('should return enrollmentEndDate on isValidEnrollmentEndDate',() =>{ + it('should create a instance of component', () => { + expect(component).toBeTruthy(); + }); + + it('should return enrollmentEndDate on isValidEnrollmentEndDate', () => { const result = component.isValidEnrollmentEndDate(true); expect(result).toEqual(true); }); - - describe('isEnrollmentAllowed',() => { + + describe('isEnrollmentAllowed', () => { it('should return true if enrollment end date is before today', () => { const enrollmentEndDate = '2024-03-26'; expect(component.isEnrollmentAllowed(enrollmentEndDate)).toBeTruthy(); @@ -157,55 +204,55 @@ describe('CourseConsumptionHeaderComponent', () => { const enrollmentEndDate = '2024-03-27'; expect(component.isEnrollmentAllowed(enrollmentEndDate)).toBeTruthy(); }); - + it('should return false if enrollment end date is after today', () => { const enrollmentEndDate = '2024-03-28'; expect(component.isEnrollmentAllowed(enrollmentEndDate)).toBeTruthy(); }); - }); - - it('should navigate to discussion forum with correct query parameters', () => { + }); + + it('should navigate to discussion forum with correct query parameters', () => { const routerData = { - forumIds: [123, 456], - userId: 'user123' + forumIds: [123, 456], + userId: 'user123' }; component.assignForumData(routerData); - + expect(mockRouter.navigate).toHaveBeenCalledWith(['/discussion-forum'], { - queryParams: { - categories: JSON.stringify({ result: routerData.forumIds }), - userId: routerData.userId - } + queryParams: { + categories: JSON.stringify({ result: routerData.forumIds }), + userId: routerData.userId + } }); }); - - describe('deleteCollection',() =>{ + + describe('deleteCollection', () => { it('should delete collection successfully', () => { - jest.spyOn(mockContentManagerService as any,'deleteContent').mockReturnValue(of({})); + jest.spyOn(mockContentManagerService as any, 'deleteContent').mockReturnValue(of({})); component.deleteCollection({ identifier: 'collection_id' }); - + expect(component.disableDelete).toBeTruthy(); expect(mockContentManagerService.deleteContent).toHaveBeenCalledWith({ request: { contents: ['collection_id'] } }); expect(mockToasterService.success).toHaveBeenCalledWith('Course deleted successfully'); }); it('should handle error while deleting collection', () => { - jest.spyOn(mockContentManagerService,'deleteContent').mockReturnValue(throwError({})); + jest.spyOn(mockContentManagerService, 'deleteContent').mockReturnValue(throwError({})); component.deleteCollection({ identifier: 'collection_id' }); - + expect(component.disableDelete).toBeFalsy(); // disableDelete should be set to false due to error expect(mockContentManagerService.deleteContent).toHaveBeenCalledWith({ request: { contents: ['collection_id'] } }); expect(mockToasterService.error).toHaveBeenCalledWith('Error deleting course'); }); - }); - - describe('downloadCollection',() =>{ + }); + + describe('downloadCollection', () => { it('should set download status and call startDownload method', () => { const collection = { - identifier: '123', - name: 'Sample Collection' + identifier: '123', + name: 'Sample Collection' }; - jest.spyOn(component.contentManagerService as any,'startDownload' as any).mockReturnValue(of({})); + jest.spyOn(component.contentManagerService as any, 'startDownload' as any).mockReturnValue(of({})); component.downloadCollection(collection); expect(component.showDownloadLoader).toBe(false); @@ -215,12 +262,12 @@ describe('CourseConsumptionHeaderComponent', () => { expect(component.contentManagerService.failedContentName).toBe('Sample Collection'); expect(component.contentManagerService.startDownload).toHaveBeenCalled(); }); - + it('should handle error if download fails due to low disk space', () => { - jest.spyOn(component.contentManagerService as any,'startDownload' as any).mockReturnValue(throwError({ error: { params: { err: 'LOW_DISK_SPACE' } } })); + jest.spyOn(component.contentManagerService as any, 'startDownload' as any).mockReturnValue(throwError({ error: { params: { err: 'LOW_DISK_SPACE' } } })); const collection = { - identifier: '123', - name: 'Sample Collection' + identifier: '123', + name: 'Sample Collection' }; component.downloadCollection(collection); expect(component.showDownloadLoader).toBe(false); @@ -228,47 +275,56 @@ describe('CourseConsumptionHeaderComponent', () => { }); it('should handle error if download fails due to other reasons', () => { - jest.spyOn(component.contentManagerService as any,'startDownload' as any).mockReturnValue(throwError({})); + jest.spyOn(component.contentManagerService as any, 'startDownload' as any).mockReturnValue(throwError({ error: { params: { err: 'SOME_OTHER_REASON' } } })); const collection = { - identifier: '123', - name: 'Sample Collection' + identifier: '123', + name: 'Sample Collection' }; component.downloadCollection(collection); expect(component.showDownloadLoader).toBe(false); expect(component.disableDelete).toBe(true); }); - }); - - it('should set values and call methods on isYoutubeContentPresent',() =>{ - const mockCollection = {data: 'mock-data'}; - jest.spyOn(component,'logTelemetry'); + }); + + it('should set values and call methods on isYoutubeContentPresent', () => { + const mockCollection = { data: 'mock-data' }; + jest.spyOn(component, 'logTelemetry'); component.showModal = false; - jest.spyOn(component.contentManagerService as any,'startDownload').mockReturnValue(of({})); - jest.spyOn(component,'downloadCollection') + jest.spyOn(component.contentManagerService as any, 'startDownload').mockReturnValue(of({})); + jest.spyOn(component, 'downloadCollection') component.isYoutubeContentPresent(mockCollection); - expect(component.logTelemetry).toHaveBeenCalledWith('is-youtube-in-collection'); + expect(component.logTelemetry).toHaveBeenCalledWith('is-youtube-in-collection'); expect(component['offlineCardService'].isYoutubeContent).toHaveBeenCalledWith(mockCollection); expect(component.downloadCollection).toHaveBeenCalledWith(mockCollection); }); - - describe('exportCollection',() => { + it('should call getFormData for the gloabal menubar', () => { + jest.spyOn(mockFormService, 'getFormConfig').mockReturnValue(of(MockResponseData.menubar.data.fields)); + component.getFormData(); + expect(component.batchEndCounter).toBe(4); + }); + it('should call getFormData for the gloabal menubar and no batchEndCounter', () => { + jest.spyOn(mockFormService, 'getFormConfig').mockReturnValue(of(MockResponseData.menubarNew.data.fields)); + component.getFormData(); + expect(component.batchEndCounter).toBe(null); + }); + describe('exportCollection', () => { it('should call exportContent method and show success message on successful export', () => { const collection = { - identifier: '123' + identifier: '123' }; - jest.spyOn(component.contentManagerService as any,'exportContent').mockReturnValue(of({})); + jest.spyOn(component.contentManagerService as any, 'exportContent').mockReturnValue(of({})); component.exportCollection(collection); expect(component.showExportLoader).toBe(false); expect(component.contentManagerService.exportContent).toHaveBeenCalledWith('123'); expect(component.toasterService.success).toHaveBeenCalledWith(component.resourceService.messages.smsg.m0059); expect(component.showExportLoader).toBe(false); }); - + it('should call exportContent method and show error message on failed export with responseCode other than NO_DEST_FOLDER', () => { - jest.spyOn(mockContentManagerService as any,'exportContent').mockReturnValue(throwError({ error: { responseCode: 'SOME_ERROR' } })); + jest.spyOn(mockContentManagerService as any, 'exportContent').mockReturnValue(throwError({ error: { responseCode: 'SOME_ERROR' } })); const collection = { - identifier: '123' + identifier: '123' }; component.exportCollection(collection); expect(component.showExportLoader).toBe(false); @@ -278,9 +334,9 @@ describe('CourseConsumptionHeaderComponent', () => { }); it('should call exportContent method and show no destination folder error message on failed export with responseCode NO_DEST_FOLDER', () => { - jest.spyOn(mockContentManagerService as any,'exportContent').mockReturnValue(throwError({ error: { responseCode: 'NO_DEST_FOLDER' } })); + jest.spyOn(mockContentManagerService as any, 'exportContent').mockReturnValue(throwError({ error: { responseCode: 'NO_DEST_FOLDER' } })); const collection = { - identifier: '123' + identifier: '123' }; component.exportCollection(collection); expect(component.showExportLoader).toBe(false); @@ -288,5 +344,125 @@ describe('CourseConsumptionHeaderComponent', () => { expect(component.toasterService.error).not.toHaveBeenCalled(); expect(component.showExportLoader).toBe(false); }); - }); -}); \ No newline at end of file + }); + describe('getAllBatchDetails', () => { + it('should call the getAllBatchDetails method with the course id', () => { + component.courseId = 'do_213878753577951232165'; + jest.spyOn(mockCourseBatchService as any, 'getAllBatchDetails').mockReturnValue((of(MockResponseData.batchList))); + component.getAllBatchDetails(); + expect(JSON.stringify(component.batchList)).toBe(JSON.stringify(MockResponseData.batchList.result.response.content)) + }); + + it('should call the getAllBatchDetails method with the course id and should throw error', () => { + component.courseId = 'do_213878753577951232165'; + jest.spyOn(mockCourseBatchService as any, 'getAllBatchDetails').mockReturnValue(throwError({ error: { params: { err: 'SOME_OTHER_REASON' } } })); + component.getAllBatchDetails(); + expect(component.showError).toBeTruthy(); + expect(mockToasterService.error).toBeCalled(); + expect(mockToasterService.error).toHaveBeenCalledWith('Could not fetch data, try again later'); + }); + }); + describe('updateCollection', () => { + it('should call the updateCollection method ', () => { + jest.spyOn(mockContentManagerService as any, 'updateContent').mockReturnValue(of({ data: 1234 })); + const collection = { + identifier: 'do_213878753577951232165' + } + component.updateCollection(collection); + expect(component.showUpdate).toBeFalsy(); + }); + it('should call the updateCollection method with error ', () => { + jest.spyOn(mockContentManagerService as any, 'updateContent').mockReturnValue(throwError({ error: { params: { err: '123' } } })); + const collection = { + identifier: 'do_213878753577951232165' + } + component.updateCollection(collection); + expect(component.showUpdate).toBeTruthy(); + }); + it('should call the updateCollection method with error and isConnected as false', () => { + component.isConnected = false; + jest.spyOn(mockContentManagerService as any, 'updateContent').mockReturnValue(throwError({ error: { params: { err: '123' } } })); + const collection = { + identifier: 'do_213878753577951232165', + name: 'ABCD' + } + component.updateCollection(collection); + expect(component.showUpdate).toBeTruthy(); + expect(mockToasterService.error).toHaveBeenCalledWith('You should be online to update the ABCD'); + }); + + + }); + describe('checkStatus and checkDownloadStatus', () => { + it('should call the checkStatus method ', () => { + jest.spyOn(component, 'checkDownloadStatus'); + component.courseHierarchy = MockResponseData.contentHeaderData.downloadList + const value = component.checkStatus({}); + expect(component.checkDownloadStatus).toBeCalled(); + }); + }); + describe('generateDataForDF', () => { + it('should call the generateDataForDF method isCreator is true ', () => { + component.generateDataForDF(); + component.courseHierarchy = MockResponseData.contentHeaderData.downloadList + expect(JSON.stringify(component.fetchForumIdReq)).toBe(JSON.stringify({ type: 'course', identifier: ['do_213878753577951232165'] })); + }); + it('should call the generateDataForDF method ', () => { + component.courseHierarchy = MockResponseData.contentHeaderData.contentList + component.enrolledCourse = true; + component.batchId = '1234567890123'; + component.generateDataForDF(); + expect(JSON.stringify(component.fetchForumIdReq)).toBe(JSON.stringify({ type: 'batch', identifier: ['1234567890123'] })); + }); + it('should call the generateDataForDF method with the mentor as true ', () => { + component.courseHierarchy = MockResponseData.contentHeaderData.contentList + jest.spyOn(mockPermissionService, 'checkRolesPermissions').mockReturnValue(true); + component.enrolledCourse = false; + component.generateDataForDF(); + expect(JSON.stringify(component.fetchForumIdReq)).toBe(JSON.stringify({ type: 'course', identifier: ['do_213878753577951232165'] })); + }); + }); + describe("ngOnDestroy", () => { + it('should destroy sub', () => { + component.unsubscribe = { + next: jest.fn(), + complete: jest.fn() + } as any; + component.ngOnDestroy(); + expect(component.unsubscribe.next).toHaveBeenCalled(); + expect(component.unsubscribe.complete).toHaveBeenCalled(); + }); + }); + describe("getCustodianOrgUser", () => { + it('should call the getCustodianOrgUser method', () => { + const custodianOrg = { + result: { response: { value: 'ROOT_ORG' } } + } + mockOrgDetailsService.getCustodianOrgDetails = jest.fn().mockReturnValue(of(custodianOrg)) as any; + component['getCustodianOrgUser'](); + expect(component.isCustodianOrgUser).toBeFalsy(); + }); + it('should call the getCustodianOrgUser method', () => { + const custodianOrg = { + result: { response: { value: 'sample-root-id' } } + } + mockOrgDetailsService.getCustodianOrgDetails = jest.fn().mockReturnValue(of(custodianOrg)) as any; + component['getCustodianOrgUser'](); + expect(component.isCustodianOrgUser).toBeTruthy(); + }); + }); + xdescribe("ngOnInit", () => { + it('should call the ngOnInit method ', () => { + const custodianOrg = { + result: { response: { value: 'sample-root-id' } } + } + mockConnectionService.monitor = jest.fn().mockReturnValue(of(true)) as any; + mockOrgDetailsService.getCustodianOrgDetails = jest.fn().mockReturnValue(of(custodianOrg)) as any; + mockConnectionService.monitor = jest.fn().mockReturnValue(of(true)) as any; + component.ngOnInit(); + + }); + }); +}); + + diff --git a/src/app/client/src/app/modules/learn/components/course-consumption/course-consumption-header/course-consumption-header.spec.data.ts b/src/app/client/src/app/modules/learn/components/course-consumption/course-consumption-header/course-consumption-header.spec.data.ts index fd4f847a95e..aa11219a200 100644 --- a/src/app/client/src/app/modules/learn/components/course-consumption/course-consumption-header/course-consumption-header.spec.data.ts +++ b/src/app/client/src/app/modules/learn/components/course-consumption/course-consumption-header/course-consumption-header.spec.data.ts @@ -54,6 +54,39 @@ export const MockResponseData = { contentHeaderData: { downloadList: { + 'id': 'api.content.download.list', + 'ver': '1.0', + 'ts': '2020-01-09T09:36:46.405Z', + 'createdBy':'sample-uid', + 'params': { + 'resmsgid': '3580f558-7f76-4fc2-8b8a-4c568f321e27', + 'msgid': '7669e658-9248-459c-be11-c9f83efc9e1a', + 'status': 'successful', + 'err': null, + 'errmsg': null + }, + 'responseCode': 'OK', + 'result': { + 'response': { + 'contents': [ + { + 'id': '264c47da-2436-4652-9214-b89ad11778f0', + 'contentId': 'do_31276385539644620815652', + 'identifier': 'do_31276385539644620815652', + 'resourceId': 'do_31275241153060864018150', + 'mimeType': 'application/vnd.ekstep.content-collection', + 'name': '10 ವಿಜ್ಞಾನ ಭಾಗ 2', + 'status': 'completed', + 'createdOn': 1578562459472, + 'pkgVersion': 3, + 'contentType': 'TextBook', + 'addedUsing': 'download' + } + ] + } + } + }, + contentList:{ 'id': 'api.content.download.list', 'ver': '1.0', 'ts': '2020-01-09T09:36:46.405Z', @@ -633,5 +666,234 @@ export const MockResponseData = { } }, + }, + batchList: { + "result": { + "response": { + "count": 1, + "content": [ + { + + "endDate": null, + "description": "", + "oldCreatedDate": null, + "updatedDate": null, + "batchId": "0138805854730977280", + "cert_templates": { + "do_213878753577951232165": { + "identifier": "do_213878753577951232165", + "previewUrl": "https://obj.stage.sunbirded.org/sunbird-content-staging/content/do_213878753577951232165/artifact/do_213878753577951232165_1694183793991_certificate_2023-09-08_20_06.svg", + "criteria": { + "assessment": { + "score": { + ">=": 20 + } + }, + "enrollment": { + "status": 2 + } + }, + "name": "New Training certificate", + "issuer": { + "name": "Gujarat Council of Educational Research and Training", + "url": "https://gcert.gujarat.gov.in/gcert/" + }, + "url": "https://obj.stage.sunbirded.org/sunbird-content-staging/content/do_213878753577951232165/artifact/do_213878753577951232165_1694183793991_certificate_2023-09-08_20_06.svg", + + } + }, + "oldEndDate": null, + "oldEnrollmentEndDate": null, + "id": "0138805854730977280", + "courseId": "do_21387643435365171217", + "collectionId": "do_21387643435365171217", + "identifier": "0138805854730977280", + "createdFor": [ + "01269878797503692810" + ], + "oldUpdatedDate": null, + "tandc": null, + "createdDate": "2023-09-11T04:27:28.749Z", + "createdBy": "4cd4c690-eab6-4938-855a-447c7b1b8ea9", + "mentors": [], + "name": "Title Content", + "oldStartDate": null, + "enrollmentType": "open", + "enrollmentEndDate": null, + "startDate": "2023-09-11T00:00:00.000Z", + "status": 1 + } + ] + } + } + }, + menubar:{ + "data": { + "action": "list", + "fields": [ + { + "index": 6, + "search": { + "fields": [ + "name", + "appIcon", + "mimeType", + "cropcategory", + + ], + "facets": [ + "croptype", + "creator", + "organisation" + ], + "filters": { + "status": [ + "Live" + ], + "contentType": [ + "Course" + ], + "primaryCategory": [ + "Course", + "Course Assessment" + ] + } + }, + "contentType": "course", + "title": "frmelmnts.tab.courses", + "isLoginMandatory": false, + "isEnabled": true, + "anonumousUserRoute": { + "route": "/explore", + "queryParam": "course" + }, + "loggedInUserRoute": { + "route": "/resources", + "queryParam": "course" + }, + "theme": { + "className": "courses", + "baseColor": "", + "textColor": "", + "supportingColor": "", + "imageName": "courses-banner-img.svg" + }, + "batchEndCounter": 4, + "desc": "frmelmnts.tab.courses", + "isOnlineOnly": true, + "menuType": "Content", + "metaData": { + "defaultFilters": { + "farmingtype": [], + "board": [ + "agriculture_framework_20" + ], + "cropcategory": [ + "Class 10" + ] + }, + "cacheTimeout": 86400000, + "groupByKey": "croptype", + "filters": [ + "board", + "cropcategory", + "croptype", + "farmingtype", + "publisher", + "audience", + "channel", + "creator", + "organisation" + ], + + } + }, + ], + "templateName": "menuConfig" + } + }, + menubarNew:{ + "data": { + "action": "list", + "fields": [ + { + "index": 6, + "search": { + "fields": [ + "name", + "appIcon", + "mimeType", + "cropcategory", + + ], + "facets": [ + "croptype", + "creator", + "organisation" + ], + "filters": { + "status": [ + "Live" + ], + "contentType": [ + "Course" + ], + "primaryCategory": [ + "Course", + "Course Assessment" + ] + } + }, + "contentType": "course", + "title": "frmelmnts.tab.courses", + "isLoginMandatory": false, + "isEnabled": true, + "anonumousUserRoute": { + "route": "/explore", + "queryParam": "course" + }, + "loggedInUserRoute": { + "route": "/resources", + "queryParam": "course" + }, + "theme": { + "className": "courses", + "baseColor": "", + "textColor": "", + "supportingColor": "", + "imageName": "courses-banner-img.svg" + }, + "desc": "frmelmnts.tab.courses", + "isOnlineOnly": true, + "menuType": "Content", + "metaData": { + "defaultFilters": { + "farmingtype": [], + "board": [ + "agriculture_framework_20" + ], + "cropcategory": [ + "Class 10" + ] + }, + "cacheTimeout": 86400000, + "groupByKey": "croptype", + "filters": [ + "board", + "cropcategory", + "croptype", + "farmingtype", + "publisher", + "audience", + "channel", + "creator", + "organisation" + ], + + } + }, + ], + "templateName": "menuConfig" + } } }; diff --git a/src/app/client/src/app/modules/learn/services/assessment/assessment-score.service.spec.ts b/src/app/client/src/app/modules/learn/services/assessment/assessment-score.service.spec.ts new file mode 100644 index 00000000000..4835a739952 --- /dev/null +++ b/src/app/client/src/app/modules/learn/services/assessment/assessment-score.service.spec.ts @@ -0,0 +1,269 @@ + +/** +* Description. +* This spec file was created using ng-test-barrel plugin! +* +*/ +import { CourseProgressService } from '../courseProgress/course-progress.service'; +import { _ } from 'lodash-es'; +import { AssessmentScoreService } from './assessment-score.service'; +import { of } from 'rxjs'; + +describe('AssessmentScoreService', () => { + let service: AssessmentScoreService; + + const courseProgressService: Partial = { + sendAssessment: jest.fn().mockReturnValue(of()) + }; + + beforeAll(() => { + service = new AssessmentScoreService( + courseProgressService as CourseProgressService + ) + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + + it('should create a instance of service', () => { + expect(service).toBeTruthy(); + }); + + it('should process telemetry events if initialized', () => { + service['initialized'] = true; + const event = { + detail: { + telemetryData: { + eid: 'END', + object: { id: 'sample-content-id' } + } + } + }; + service.receiveTelemetryEvents(event) + expect(service['initialized']).toBeTruthy() + }) + + it('should not process telemetry events if not initialized', () => { + service['initialized'] = false; + const event = { + detail: { + telemetryData: { + eid: 'END', + object: { id: 'sample-content-id' } + } + } + }; + service.receiveTelemetryEvents(event) + expect(service['initialized']).toBeFalsy(); + }) + + describe('handleReviewButtonClickEvent', () => { + it('should clear assess events', () => { + service['_assessEvents'] = ['event1', 'event2', 'event3']; + service.handleReviewButtonClickEvent(); + expect(service['_assessEvents']).toEqual([]); + }); + }); + it('initilize the assessment service', () => { + let batchDetails = { + batchId: 1234 + } + let courseDetails = { + courseId: 1234 + } + let contentDetails = { + contentId: 1234 + } + service['_batchDetails'] = batchDetails + service['_courseDetails'] = courseDetails + service['_contentDetails'] = contentDetails + + service.init({ batchDetails, courseDetails, contentDetails }); + service['checkContentForAssessment'] + }) + + it('handles submit button clicked in course player', () => { + const clicked = true; + service['_startEvent'] = true; + service['initialized'] = true; + service['processAssessEvents'] = jest.fn(); + service.handleSubmitButtonClickEvent(clicked); + expect(service['processAssessEvents']).toHaveBeenCalled(); + + }) + + it('process the telemetry Events for eid as END', () => { + const event = { + detail: { + telemetryData: { + eid: 'END', + edata: "edata", + object: { id: 'sample-content-id' } + } + } + }; + const eventData = event.detail.telemetryData; + const eid = eventData.eid; + const edata = eventData.edata + + service['processTelemetryEvents'](event) + }) + + it('process the telemetry Events for eid as START', () => { + const event = { + detail: { + telemetryData: { + eid: 'START', + edata: "edata", + ets: 'ets', + actor: { + id: '123' + }, + object: { id: 'sample-content-id' } + } + } + }; + const eventData = event.detail.telemetryData; + const eid = eventData.eid; + const edata = eventData.edata + service['_startEvent'] = eventData + service['._assessmentTs'] = eventData.ets + service['_userId'] = eventData.actor.id + service['_assessEvents'] = [] + + service['processTelemetryEvents'](event) + }) + + it('process the telemetry Events for eid as INTERACT', () => { + const event = { + detail: { + telemetryData: { + eid: 'INTERACT', + edata: { + id: 'Review_button' + }, + ets: 'ets', + actor: { + id: '123' + }, + object: { id: 'sample-content-id' } + } + } + }; + service['_assessEvents'] = [] + + service['processTelemetryEvents'](event) + }) + + it('process the telemetry Events for eid as ASSESS', () => { + const event = { + detail: { + telemetryData: { + eid: 'ASSESS', + edata: { + id: 'Review_button' + }, + ets: 'ets', + actor: { + id: '123' + }, + object: { id: 'sample-content-id' } + } + } + }; + service['_assessEvents'] = [event] + service['processTelemetryEvents'](event) + }) + + it('checks if the course has assessment or not', () => { + let batchDetails = { + batchId: 1234, + courseId: 'c123' + } + let courseDetails = { + courseId: 1234 + } + let contentDetails = { + identifier: 1234 + } + service['_batchDetails'] = batchDetails + service['_courseDetails'] = courseDetails + service['_contentDetails'] = contentDetails + service['initialized'] = true + service['checkContentForAssessment']() + expect(service['_batchDetails']).toBeDefined(); + expect(service['_courseDetails']).toBeDefined(); + expect(service['_contentDetails']).toBeDefined(); + expect(service['initialized']).toBeTruthy(); + }) + describe('updateAssessmentScore', () => { + it('should call courseProgressService.sendAssessment with the correct arguments', () => { + const mockRequest = {}; + service['updateAssessmentScore'](mockRequest); + expect(courseProgressService.sendAssessment).toHaveBeenCalledWith({ requestBody: mockRequest, methodType: 'PATCH' }); + + }); + }); + + describe('prepareRequestObject', () => { + it('should construct the request object with the correct properties', () => { + const attemptId = 'attempt123'; + const request = service['prepareRequestObject'](attemptId); + expect(request).toEqual({ + request: { + userId: '123', + contents: [{ + contentId: 1234, + batchId: 1234, + status: 2, + courseId: 'c123', + lastAccessTime: expect.any(String) + }], + assessments: [{ + assessmentTs: 'ets', + batchId: 1234, + courseId: 'c123', + userId: "123", + attemptId: 'attempt123', + contentId: 1234, + events: [ + { + detail: { + telemetryData: { + actor: { + id: "123", + }, + edata: { + id: "Review_button", + }, + eid: "ASSESS", + ets: "ets", + object: { + "id": "sample-content-id", + }, + }, + }, + }, + { + actor: { + id: "123", + }, + edata: { + id: "Review_button", + }, + eid: "ASSESS", + ets: "ets", + object: { + id: "sample-content-id", + }, + }, + ] + }] + } + + }); + }) + }); +}); \ No newline at end of file diff --git a/src/app/client/src/app/modules/learn/services/course-batch/course-batch.service.spec.data.ts b/src/app/client/src/app/modules/learn/services/course-batch/course-batch.service.spec.data.ts new file mode 100644 index 00000000000..040706b01b8 --- /dev/null +++ b/src/app/client/src/app/modules/learn/services/course-batch/course-batch.service.spec.data.ts @@ -0,0 +1,179 @@ +export const mockData = { + batchData: { + 'id': 'api.course.batch.read', + 'ver': 'v1', + 'ts': '2020-08-24 11:08:46:289+0000', + 'params': { + 'resmsgid': null, + 'msgid': '8dc7dc3d-82b9-311b-f610-8ccefded4460', + 'err': null, + 'status': 'success', + 'errmsg': null + }, + 'responseCode': 'OK', + 'result': { + 'response': { + 'identifier': '01307963745936998440', + 'createdFor': [ + '0124784842112040965' + ], + 'endDate': null, + 'description': '', + 'cert_templates': { + 'template_21': { + 'identifier': 'mock_cert_identifier', + 'data' : `{'artifactUrl': 'https://cert.svg',}`, + 'criteria': { + 'user': { + 'rootOrgId': '0124784842112040965' + }, + 'enrollment': { + 'status': 2 + }, + 'score': { + '>=': 40 + } + }, + 'name': 'Course completion certificate', + 'notifyTemplate': { + 'emailTemplateType': 'defaultCertTemp', + 'subject': 'Completion certificate', + 'stateImgUrl': 'https://s.png', + 'regards': 'Minister of Gujarat', + 'regardsperson': 'Chairperson' + }, + 'issuer': { + 'name': 'Research and Training', + 'url': 'https://gcert/' + }, + 'signatoryList': [ + { + 'image': 'https://signature-523237__340.jpg', + 'name': 'CEO', + 'id': 'CEO', + 'designation': 'CEO' + } + ] + } + }, + 'batchId': '01307963745936998440', + 'createdDate': '2020-08-05 13:37:52:083+0000', + 'createdBy': 'ab467e6e-1f32-453c-b1d8-c6b5fa6c7b9e', + 'mentors': [], + 'name': 'Sudip Mukherjee', + 'id': '01307963745936998440', + 'enrollmentType': 'open', + 'courseId': 'do_21307528604532736012398', + 'enrollmentEndDate': null, + 'startDate': '2020-08-05', + 'status': 1 + } + } + }, + batchDataNew: { + 'id': 'api.course.batch.read', + 'ver': 'v1', + 'ts': '2020-08-24 11:08:46:289+0000', + 'params': { + 'resmsgid': null, + 'msgid': '8dc7dc3d-82b9-311b-f610-8ccefded4460', + 'err': null, + 'status': 'success', + 'errmsg': null + }, + 'responseCode': 'OK', + 'result': { + 'response': { + 'identifier': '01307963745936998440', + 'createdFor': [ + '0124784842112040965' + ], + 'endDate': null, + 'description': '', + 'cert_templates': { + 'template_21': { + 'identifier': 'mock_cert_identifier', + 'data' : `{'artifactUrl': 'https://cert.svg',}`, + 'description':'certificate for completion', + 'criteria': { + 'user': { + 'rootOrgId': '0124784842112040965' + }, + 'enrollment': { + 'status': 2 + }, + 'score': { + '>=': 40 + } + }, + 'name': 'Course completion certificate', + 'notifyTemplate': { + 'emailTemplateType': 'defaultCertTemp', + 'subject': 'Completion certificate', + 'stateImgUrl': 'https://s.png', + 'regards': 'Minister of Gujarat', + 'regardsperson': 'Chairperson' + }, + 'issuer': { + 'name': 'Research and Training', + 'url': 'https://gcert/' + }, + 'signatoryList': [ + { + 'image': 'https://signature-523237__340.jpg', + 'name': 'CEO', + 'id': 'CEO', + 'designation': 'CEO' + } + ] + } + }, + 'batchId': '01307963745936998440', + 'createdDate': '2020-08-05 13:37:52:083+0000', + 'createdBy': 'ab467e6e-1f32-453c-b1d8-c6b5fa6c7b9e', + 'mentors': [], + 'name': 'Sudip Mukherjee', + 'id': '01307963745936998440', + 'enrollmentType': 'open', + 'courseId': 'do_21307528604532736012398', + 'enrollmentEndDate': null, + 'startDate': '2020-08-05', + 'status': 1 + } + } + }, + userProfile: { + 'maskedPhone': '******2507', + 'rootOrgName': 'CustROOTOrg10', + 'roleOrgMap':{ + 'CONTENT_CREATOR':'CustROOTOrg10' + }, + 'identifier': 'd8bfe598-21c8-4c9c-b335-a3f75a97a988', + 'thumbnail': null, + 'profileVisibility': {}, + 'rootOrgId': '01285019302823526477', + 'prevUsedEmail': '', + 'firstName': 'dev-user13', + 'userName': 'devuser13', + 'userId': 'd8bfe598-21c8-4c9c-b335-a3f75a97a988', + 'promptTnC': false, + 'emailVerified': true + }, + userProfileNew: { + 'maskedPhone': '******2507', + 'id': 'd8bfe598-21c8-4c9c-b335-a3f75a97a988', + 'recoveryEmail': '', + 'roleOrgMap':{ + 'CONTENT_CREATOR':['01285019302823526477'] + }, + 'identifier': 'd8bfe598-21c8-4c9c-b335-a3f75a97a988', + 'thumbnail': null, + 'profileVisibility': {}, + 'rootOrgId': '01285019302823526477', + 'userName': 'devuser13', + 'userId': 'd8bfe598-21c8-4c9c-b335-a3f75a97a988', + 'promptTnC': false, + 'emailVerified': true + }, + +}; \ No newline at end of file diff --git a/src/app/client/src/app/modules/learn/services/course-batch/course-batch.service.spec.ts b/src/app/client/src/app/modules/learn/services/course-batch/course-batch.service.spec.ts new file mode 100644 index 00000000000..c3eeaad9bb9 --- /dev/null +++ b/src/app/client/src/app/modules/learn/services/course-batch/course-batch.service.spec.ts @@ -0,0 +1,242 @@ + +/** +* Description. +* This spec file was created using ng-test-barrel plugin! +* +*/ + +import { Observable, observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Injectable, EventEmitter } from '@angular/core'; +import { ConfigService, ServerResponse } from '@sunbird/shared'; +import { SearchParam, LearnerService, UserService, ContentService, SearchService } from '@sunbird/core'; +import { _ } from 'lodash-es'; +import { CourseBatchService } from './course-batch.service'; +import { mockData } from './course-batch.service.spec.data'; +import { isNullOrUndefined } from '@swimlane/ngx-datatable'; +describe('CourseBatchService', () => { + let component: CourseBatchService; + + const searchService: Partial = {}; + const userService: Partial = { + loggedIn: true, + slug: jest.fn().mockReturnValue('tn') as any, + userData$: of({userProfile: { + userId: 'sample-uid', + rootOrgId: 'sample-root-id', + rootOrg: {}, + roleOrgMap:{ + CONTENT_CREATOR:'CONTENT_CREATOR' + }, + hashTagIds: ['id'] + } as any}) as any, + setIsCustodianUser: jest.fn(), + setGuestUser: jest.fn(), + userid: 'sample-uid', + appId: 'sample-id', + getServerTimeDiff: '', + }; + const content: Partial = {}; + const configService: Partial = { + urlConFig: { + URLS: { + BATCH: { + GET_BATCHS: 'course/v1/batch/list', + GET_DETAILS:'course/v1/batch/read', + REMOVE_USERS:'course/v1/batch/user/remove', + ADD_USERS:'course/v1/batch/user/add', + UPDATE: 'course/v1/batch/update', + CREATE: 'course/v1/batch/create', + GET_PARTICIPANT_LIST: 'course/v1/batch/participants/list' + }, + COURSE:{ + UNENROLL_USER_COURSE:'course/v1/unenrol' + }, + ADMIN:{ + USER_SEARCH:'user/v3/search' + } + } + } + }; + const learnerService: Partial = { + post: jest.fn(), + get:jest.fn(), + patch:jest.fn() + }; + + beforeAll(() => { + component = new CourseBatchService( + searchService as SearchService, + userService as UserService, + content as ContentService, + configService as ConfigService, + learnerService as LearnerService + ) + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + + it('should create a instance of component', () => { + expect(component).toBeTruthy(); + }); + it('should create a instance of component and call the getBatchDetails method', () => { + jest.spyOn(component.learnerService, 'get').mockReturnValue(of(mockData.batchData)); + const batchId = '123456'; + component.getBatchDetails(batchId); + expect(component.learnerService.get).toBeCalled(); + }); + it('should create a instance of component and call the removeUsersFromBatch method', () => { + const batchId = '123456'; + const request = {user:'remove'} + component.removeUsersFromBatch(batchId,request); + expect(component.learnerService.post).toBeCalled(); + }); + it('should create a instance of component and call the addUsersToBatch method', () => { + const batchId = '123456'; + const request = {user:'add'} + component.addUsersToBatch(batchId,request); + expect(component.learnerService.post).toBeCalled(); + }); + it('should create a instance of component and call the updateBatch method', () => { + const request = {update:'true'} + component.updateBatch(request); + expect(component.learnerService.patch).toBeCalled(); + }); + it('should create a instance of component and call the createBatch method', () => { + const request = {update:'true'} + component.createBatch(request); + expect(component.learnerService.post).toBeCalled(); + }); + it('should create a instance of component and call the unenrollFromCourse method', () => { + const data = {unenrollFromCourse:'true'} + component.unenrollFromCourse(data); + expect(component.learnerService.post).toBeCalled(); + }); + it('should create a instance of component and call the enrollToCourse method', () => { + const data = {enrollToCourse:'true'} + component.enrollToCourse(data); + expect(component.learnerService.post).toBeCalled(); + }); + it('should create a instance of component and call the getParticipantList method', () => { + jest.spyOn(component.learnerService, 'post').mockReturnValue(of(mockData.batchData)); + const data = {participentList:'get'} + component.getParticipantList(data).subscribe(obj=>{}); + expect(component.learnerService.post).toBeCalled(); + }); + it('should create a instance of component and call the getUpdateBatchDetails method', () => { + component['_updateBatchDetails'] = mockData.batchDataNew.result.response + const value = component.getUpdateBatchDetails(mockData.batchDataNew.result.response.batchId).subscribe(obj=>{ + expect(JSON.stringify(obj)).toEqual(JSON.stringify(of(mockData.batchDataNew.result.response))); + }); + + }); + it('should create a instance of component and call the getUpdateBatchDetails method without _updateBatchDetails', () => { + component['_updateBatchDetails'] = null; + jest.spyOn(component,'getBatchDetails').mockReturnValue(of(mockData.batchData)); + const value = component.getUpdateBatchDetails(mockData.batchDataNew.result.response.batchId).subscribe(obj=>{}); + expect(component.getBatchDetails).toBeCalledWith(mockData.batchDataNew.result.response.batchId); + }); + it('should create a instance of component and call the getEnrollToBatchDetails method', () => { + component['_enrollToBatchDetails'] = mockData.batchDataNew.result.response + const value = component.getEnrollToBatchDetails(mockData.batchDataNew.result.response.batchId).subscribe(obj=>{ + expect(JSON.stringify(obj)).toEqual(JSON.stringify(of(mockData.batchDataNew.result.response))); + }); + + }); + it('should create a instance of component and call the getEnrollToBatchDetails method without _updateBatchDetails', () => { + component['_enrollToBatchDetails'] = null; + jest.spyOn(component,'getBatchDetails').mockReturnValue(of(mockData.batchData)); + const value = component.getEnrollToBatchDetails(mockData.batchDataNew.result.response.batchId).subscribe(obj=>{}); + expect(component.getBatchDetails).toBeCalledWith(mockData.batchDataNew.result.response.batchId); + }); + it('should create a instance of component and call the setUpdateBatchDetails method', () => { + component['_updateBatchDetails'] = null; + const value = component.setUpdateBatchDetails(mockData.batchDataNew); + expect(component['_updateBatchDetails']).toEqual(mockData.batchDataNew); + }); + it('should create a instance of component and call the setEnrollToBatchDetails method', () => { + component['_enrollToBatchDetails'] = null; + const value = component.setEnrollToBatchDetails(mockData.batchDataNew); + expect(component['_enrollToBatchDetails']).toEqual(mockData.batchDataNew); + }); + it('should create a instance of component and call the getUserList method', () => { + component['defaultUserList'] = [{user1:'abcd'},{user2:'mnop'}] + const value = component.getUserList({}); + expect(JSON.stringify(value)).toEqual(JSON.stringify(of([{user1:'abcd'},{user2:'mnop'}]))); + }); + it('should create a instance of component and call the getUserList method with requestBody having value', () => { + component['defaultUserList'] = null; + jest.spyOn(component.learnerService,'post').mockReturnValue(of(mockData.userProfile as any)as any) as any; + component.userService = { + userProfile: mockData.userProfile as any + } as any; + const requestBody = { + filters: { 'status': '1' }, + query:'newUser', + limit:100 + }; + const value = component.getUserList(requestBody).subscribe(data=>{ + expect(data).toEqual(mockData.userProfile); + }); + + }); + it('should create a instance of component and call the getUserList method with requestBody having no value', () => { + component['defaultUserList'] = null; + jest.spyOn(component.learnerService,'post').mockReturnValue(of(mockData.userProfileNew as any)as any) as any; + component.userService = { + rootOrgId:'01285019302823526477', + userProfile: mockData.userProfileNew as any + } as any; + const value = component.getUserList().subscribe(data=>{ + expect(data).toEqual(mockData.userProfileNew); + }); + + }); + + + describe('getAllBatchDetails', () => { + it('should create a instance and call the getAllBatchDetails method', () => { + jest.spyOn(learnerService, 'post'); + const searchParams: any = { + filters: { + courseId: 'do_213878753577951232165', + status: ['1'] + } + } + const value = component.getAllBatchDetails(searchParams); + expect(learnerService.post).toBeCalled(); + }); + + }); + describe('getcertificateDescription', () => { + it('should create a instance and call the getcertificateDescription method', () => { + const value = component.getcertificateDescription(mockData.batchData.result.response); + const obj = { isCertificate: true, description: '' } + expect(JSON.stringify(value)).toEqual(JSON.stringify(obj)) + }); + it('should create a instance and call the getcertificateDescription method with description', () => { + const value = component.getcertificateDescription(mockData.batchDataNew.result.response); + const obj = { isCertificate: true, description: 'certificate for completion' } + expect(JSON.stringify(value)).toEqual(JSON.stringify(obj)) + }); + }); + describe('getEnrolledBatchDetails', () => { + it('should create a instance and call the getEnrolledBatchDetails method', () => { + jest.spyOn(learnerService,'get').mockReturnValue(of(mockData.batchData)); + jest.spyOn(component,'getBatchDetails').mockReturnValue(of(mockData.batchData)); + const value = component.getEnrolledBatchDetails(mockData.batchData.result.response.batchId).subscribe(data =>{ + }); + expect(component.getBatchDetails).toBeCalled(); + }); + it('should create a instance and call the getEnrolledBatchDetails method with _enrolledBatchDetails', () => { + component['_enrolledBatchDetails'] = mockData.batchDataNew.result.response + const value = component.getEnrolledBatchDetails(mockData.batchDataNew.result.response.batchId).subscribe(data =>{ + expect(JSON.stringify(data)).toEqual(JSON.stringify(of(mockData.batchDataNew.result.response))) + }); + + }); + }); +}); \ No newline at end of file diff --git a/src/app/client/src/app/modules/learn/services/course-consumption/course-consumption.service.spec.ts b/src/app/client/src/app/modules/learn/services/course-consumption/course-consumption.service.spec.ts new file mode 100644 index 00000000000..e8c56c84a82 --- /dev/null +++ b/src/app/client/src/app/modules/learn/services/course-consumption/course-consumption.service.spec.ts @@ -0,0 +1,435 @@ + +/** +* Description. +* This spec file was created using ng-test-barrel plugin! +* +*/ +import { PlayerService, PermissionService, UserService, GeneraliseLabelService } from '@sunbird/core'; +import { ResourceService, ToasterService } from '@sunbird/shared'; +import { CourseProgressService } from '../courseProgress/course-progress.service'; +import { _ } from 'lodash-es'; +import { Router } from '@angular/router'; +import { NavigationHelperService } from '@sunbird/shared'; +import dayjs from 'dayjs'; +import { CourseConsumptionService } from './course-consumption.service'; +import { of } from 'rxjs'; + +describe('CourseConsumptionService', () => { + let service: CourseConsumptionService; + + const playerService: Partial = { + getConfigByContent: jest.fn(), + getCollectionHierarchy: jest.fn().mockReturnValue(of({ + result: { + content: { + leafNodesCount: 2, + children: [] + } + } + })) + }; + const courseProgressService: Partial = { + getContentState: jest.fn(), + updateContentsState: jest.fn(), + }; + const toasterService: Partial = { + error: jest.fn() + }; + const resourceService: Partial = {}; + const router: Partial = {}; + const navigationHelperService: Partial = { + getPreviousUrl: jest.fn().mockReturnValue({ url: '/some/previous/url' }) + }; + const permissionService: Partial = { + checkRolesPermissions: jest.fn() + }; + const userService: Partial = { + userid: 'user1' + }; + const generaliselabelService: Partial = { + messages: { + emsg: { + m0003: "Error message here", + + } + }, + }; + beforeAll(() => { + service = new CourseConsumptionService( + playerService as PlayerService, + courseProgressService as CourseProgressService, + toasterService as ToasterService, + resourceService as ResourceService, + router as Router, + navigationHelperService as NavigationHelperService, + permissionService as PermissionService, + userService as UserService, + generaliselabelService as GeneraliseLabelService + ) + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + + it('should create a instance of component', () => { + expect(service).toBeTruthy(); + }); + + it('should return course hierarchy from cache if available', () => { + const courseId = 'some-course-id'; + service.courseHierarchy = { identifier: courseId /* mock the rest of the structure as needed */ }; + const result$ = service.getCourseHierarchy(courseId); + result$.subscribe((result) => { + expect(result).toEqual(service.courseHierarchy); + }); + expect(playerService.getCollectionHierarchy).not.toHaveBeenCalled(); + }); + + it('should call getConfigByContent with provided contentId and options', () => { + const contentId = '123456'; + const options = { id: 123 }; + service.getConfigByContent(contentId, options); + expect(playerService.getConfigByContent).toHaveBeenCalledWith(contentId, options); + }); + + it('should call getContentState with the provided request', () => { + const req = { status: 'live' }; + service.getContentState(req); + expect(courseProgressService.getContentState).toHaveBeenCalledWith(req); + }); + + it('should call updateContentsState with the provided request', () => { + const req = { status: 'live' }; + service.updateContentsState(req); + expect(courseProgressService.updateContentsState).toHaveBeenCalledWith(req); + }); + + + it('should parse the course hierarchy and return content identifiers of leaf nodes with specific MIME types', () => { + const courseHierarchy = { + result: { + content: { + leafNodesCount: 2, + children: [] + } + } + }; + const mimeTypeCount = 2 + const contentIds = service.parseChildren(courseHierarchy); + expect(contentIds).toEqual([undefined]); + expect(mimeTypeCount).toEqual(2); + }); + + describe('getRollUp', () => { + it('should return an empty object if rollup is empty', () => { + // Arrange + const rollup = []; + // Act + const result = service.getRollUp(rollup); + // Assert + expect(result).toEqual({}); + }); + + it('should return an object with keys "l1", "l2", etc., corresponding to elements of rollup array', () => { + // Arrange + const rollup = ['value1', 'value2', 'value3']; + // Act + const result = service.getRollUp(rollup); + // Assert + expect(result).toEqual({ l1: 'value1', l2: 'value2', l3: 'value3' }); + }); + + it('should handle an undefined rollup', () => { + // Arrange + const rollup = undefined + // Act + const result = service.getRollUp(rollup); + // Assert + expect(result).toEqual({}); + }); + }) + + describe('getContentRollUp', () => { + const tree = { + identifier: 'root', + children: [ + { + identifier: 'child1', + children: [ + { + identifier: 'grandchild1', + children: [] + } + ] + }, + { + identifier: 'child2', + children: [ + { + identifier: 'grandchild2', + children: [ + { + identifier: 'greatGrandchild', + children: [] + } + ] + } + ] + } + ] + }; + + it('should return the rollup for the specified identifier', () => { + const identifier = 'greatGrandchild'; + const result = service.getContentRollUp(tree, identifier); + expect(result).toEqual(['root', 'child2', 'grandchild2', 'greatGrandchild']); + }); + + it('should return an empty array if the identifier is not found', () => { + const identifier = 'nonExistentChild'; + const result = service.getContentRollUp(tree, identifier); + expect(result).toEqual([]); + }); + + it('should return an empty array for an empty tree', () => { + const emptyTree = {}; + const identifier = 'someIdentifier'; + const result = service.getContentRollUp(emptyTree, identifier); + expect(result).toEqual([]); + }); + }); + + describe('Permissions', () => { + const permissionService = { + checkRolesPermissions: jest.fn(), + }; + const contentCreatorHierarchy = { + createdBy: 'user123', + }; + const courseMentorHierarchy = {}; + const nonTrackableCollection = { + trackable: { + enabled: 'no', + }, + }; + const trackableCollectionWithCertificates = { + createdBy: 'user123', + contentType: 'course', + credentials: { + enabled: 'yes', + }, + }; + describe('canCreateBatch', () => { + it('should return true if user is a content creator and created the course', () => { + expect(service.canCreateBatch(contentCreatorHierarchy)).toBe(false); + }); + it('should return false if user is not a content creator', () => { + permissionService.checkRolesPermissions.mockReturnValueOnce(false); + expect(service.canCreateBatch(courseMentorHierarchy)).toBe(false); + }); + + it('should return false if user did not create the course', () => { + const hierarchy = { createdBy: 'otherUser' }; + expect(service.canCreateBatch(hierarchy)).toBe(false); + }); + }); + + describe('canViewDashboard', () => { + it('should return true if user can create a batch or is a course mentor', () => { + service.canCreateBatch = jest.fn().mockReturnValueOnce(true); + expect(service.canViewDashboard({})).toBe(true); + }); + + it('should return true if user is a course mentor', () => { + service.canCreateBatch = jest.fn().mockReturnValueOnce(false); + permissionService.checkRolesPermissions.mockReturnValueOnce(true); + expect(service.canViewDashboard({})).toBe(undefined); + }); + + it('should return false if user cannot create a batch and is not a course mentor', () => { + service.canCreateBatch = jest.fn().mockReturnValueOnce(false); + permissionService.checkRolesPermissions.mockReturnValueOnce(false); + expect(service.canViewDashboard({})).toBe(undefined); + }); + }); + + describe('canAddCertificates', () => { + it('should return true if user can create a batch, course is trackable, and certificates are enabled', () => { + service.canCreateBatch = jest.fn().mockReturnValueOnce(true); + service.isTrackableCollection = jest.fn().mockReturnValueOnce(true); + expect(service.canAddCertificates(trackableCollectionWithCertificates)).toBe(true); + }); + + it('should return false if user cannot create a batch', () => { + service.canCreateBatch = jest.fn().mockReturnValueOnce(false); + expect(service.canAddCertificates(trackableCollectionWithCertificates)).toBe(false); + }); + + it('should return false if course is not trackable', () => { + service.isTrackableCollection = jest.fn().mockReturnValueOnce(false); + expect(service.canAddCertificates(trackableCollectionWithCertificates)).toBe(undefined); + }); + + it('should return false if certificates are not enabled', () => { + service.canCreateBatch = jest.fn().mockReturnValueOnce(true); + service.isTrackableCollection = jest.fn().mockReturnValueOnce(true); + const collectionWithoutCertificates = { ...trackableCollectionWithCertificates, credentials: { enabled: 'no' } }; + expect(service.canAddCertificates(collectionWithoutCertificates)).toBe(false); + }); + + it('should return false if course is not a trackable collection', () => { + service.canCreateBatch = jest.fn().mockReturnValueOnce(true); + service.isTrackableCollection = jest.fn().mockReturnValueOnce(false); + expect(service.canAddCertificates(nonTrackableCollection)).toBe(false); + }); + }); + + + }); + describe('CoursePagePreviousUrl', () => { + it('should return the correct previous URL', () => { + const previousUrl = 'https://example.com/previous'; + service.coursePagePreviousUrl = previousUrl; + expect(service.getCoursePagePreviousUrl).toEqual(previousUrl); + }); + }); + + describe('emitBatchList', () => { + it('should emit visibility based on mentor batches', () => { + const batches = [ + { id: 1, createdBy: 'user1', mentors: ['user2', 'user3'] }, + { id: 2, createdBy: 'user2', mentors: ['user1'] }, + { id: 3, createdBy: 'user3', mentors: ['user1', 'user2'] } + ]; + service.userCreatedAnyBatch.emit = jest.fn(); + service.emitBatchList(batches); + expect(service.userCreatedAnyBatch.emit).toHaveBeenCalledWith(true); + }); + + }); + + it('should set coursePagePreviousUrl if navigationHelperService returns valid url', () => { + service.setCoursePagePreviousUrl(); + expect(service.coursePagePreviousUrl).toEqual("https://example.com/previous"); + }); + + it('should not set coursePagePreviousUrl if navigationHelperService returns invalid url', () => { + navigationHelperService.getPreviousUrl = jest.fn().mockReturnValue({ url: '/enroll/batch/123' }); + service.setCoursePagePreviousUrl(); + expect(service.coursePagePreviousUrl).toEqual("https://example.com/previous"); + }); + + + describe('getAllOpenBatches', () => { + it('should emit false if there are no open batches and display an error message', () => { + jest.spyOn(service.enableCourseEntrollment, 'emit'); + service.getAllOpenBatches({ content: [] }); + expect(service.enableCourseEntrollment.emit).toHaveBeenCalledWith(false); + expect(toasterService.error).toHaveBeenCalledWith('Error message here'); + }); + + it('should emit true if there is at least one open batch', () => { + const currentDate = dayjs().format('YYYY-MM-DD'); + const futureDate = dayjs().add(1, 'day').format('YYYY-MM-DD'); + jest.spyOn(service.enableCourseEntrollment, 'emit'); + service.getAllOpenBatches({ content: [{ enrollmentType: 'open', enrollmentEndDate: currentDate }] }); + expect(service.enableCourseEntrollment.emit).toHaveBeenCalledWith(true); + service.getAllOpenBatches({ content: [{ enrollmentType: 'open', enrollmentEndDate: futureDate }] }); + expect(service.enableCourseEntrollment.emit).toHaveBeenCalledTimes(2); + }); + + it('should emit true if there is an open batch with no end date', () => { + jest.spyOn(service.enableCourseEntrollment, 'emit'); + service.getAllOpenBatches({ content: [{ enrollmentType: 'open' }] }); + expect(service.enableCourseEntrollment.emit).toHaveBeenCalledWith(true); + }); + + it('should emit true if there are multiple open batches with at least one having no end date', () => { + const currentDate = dayjs().format('YYYY-MM-DD'); + const futureDate = dayjs().add(1, 'day').format('YYYY-MM-DD'); + jest.spyOn(service.enableCourseEntrollment, 'emit'); + service.getAllOpenBatches({ + content: [ + { enrollmentType: 'open' }, + { enrollmentType: 'open', enrollmentEndDate: futureDate } + ] + }); + expect(service.enableCourseEntrollment.emit).toHaveBeenCalledWith(true); + }); + }) + + it('should return previous and next modules correctly', () => { + const courseHierarchy = { + children: [ + { identifier: 'module1' }, + { identifier: 'module2' }, + { identifier: 'module3' } + ] + }; + const collectionId = 'module2'; + const result = service.setPreviousAndNextModule(courseHierarchy, collectionId); + expect(result).toEqual({ + prev: { identifier: 'module1' }, + next: { identifier: 'module3' } + }); + }); + + it('should handle when there are no children in courseHierarchy', () => { + const courseHierarchy = {}; + const collectionId = 'module1'; + const result = service.setPreviousAndNextModule(courseHierarchy, collectionId); + expect(result).toEqual(undefined); + }); + + it('should handle when collectionId is not found in children', () => { + const courseHierarchy = { + children: [ + { identifier: 'module1' }, + { identifier: 'module2' }, + { identifier: 'module3' } + ] + }; + const collectionId = 'moduleX'; + const result = service.setPreviousAndNextModule(courseHierarchy, collectionId); + expect(result).toEqual({ + prev: undefined, + next: { + identifier: 'module1', + } + + }); + }); + + it('should flatten a nested array of objects into a single-dimensional array', () => { + const contents = [ + { id: 1, children: [{ id: 2 }, { id: 3 }] }, + { id: 4, children: [{ id: 5 }, { id: 6, children: [{ id: 7 }, { id: 8 }] }] } + ]; + const flattened = service.flattenDeep(contents); + const expected = [ + { id: 1, children: [{ id: 2 }, { id: 3 }] }, + { id: 2 }, + { id: 3 }, + { id: 4, children: [{ id: 5 }, { id: 6, children: [{ id: 7 }, { id: 8 }] }] }, + { id: 5 }, + { id: 6, children: [{ id: 7 }, { id: 8 }] }, + { id: 7 }, + { id: 8 } + ]; + expect(flattened).toEqual(expected); + }); + + it('should return an empty array if contents is undefined', () => { + const contents = undefined; + const flattened = service.flattenDeep(contents); + expect(flattened).toEqual(undefined); + }); + + it('should return an empty array if contents is an empty array', () => { + const contents = []; + const flattened = service.flattenDeep(contents); + expect(flattened).toEqual([]); + }); +}); diff --git a/src/app/client/src/app/modules/merge-account/components/merge-account-status/merge-account-status.component.scss b/src/app/client/src/app/modules/merge-account/components/merge-account-status/merge-account-status.component.scss index be4bb7d9b93..e63083fde09 100644 --- a/src/app/client/src/app/modules/merge-account/components/merge-account-status/merge-account-status.component.scss +++ b/src/app/client/src/app/modules/merge-account/components/merge-account-status/merge-account-status.component.scss @@ -3,4 +3,8 @@ .sb-merged-account-body-para.sb-color-success{ color:var(--success-color); +} + +.sb-certificatePage-logo { + width: calculateRem(150px); } \ No newline at end of file diff --git a/src/app/client/src/app/modules/observation/components/observation-listing/observation-listing.component.spec.ts b/src/app/client/src/app/modules/observation/components/observation-listing/observation-listing.component.spec.ts index 804754de5ad..4fd6fae8193 100644 --- a/src/app/client/src/app/modules/observation/components/observation-listing/observation-listing.component.spec.ts +++ b/src/app/client/src/app/modules/observation/components/observation-listing/observation-listing.component.spec.ts @@ -150,6 +150,7 @@ describe('ObservationListingComponent', () => { }); it('should call playcontent component', () => { + component.categoryKeys = [{code:'solution'}, {code:'solution_type'}, {code:'program'}, {code:'entityType'},{code:'programName'}] const mockEvent = { data: { programId: 'mock-program-id', @@ -167,7 +168,7 @@ describe('ObservationListingComponent', () => { solutionId: data.solutionId, observationId: data._id, solutionName: data.name, - programName: data.subject[0], + programName: data[component.categoryKeys[4].code], entityType: data.entityType }) expect(component.router.navigate).toHaveBeenCalledWith(['observation/details'], { diff --git a/src/app/client/src/app/modules/observation/components/observation-listing/observation-listing.component.ts b/src/app/client/src/app/modules/observation/components/observation-listing/observation-listing.component.ts index efc4fde7d70..81d6be52c20 100644 --- a/src/app/client/src/app/modules/observation/components/observation-listing/observation-listing.component.ts +++ b/src/app/client/src/app/modules/observation/components/observation-listing/observation-listing.component.ts @@ -338,7 +338,7 @@ export class ObservationListingComponent solutionId: data.solutionId, observationId: data._id, solutionName: data.name, - programName: data.subject[0], + programName: data[this.categoryKeys[4].code], entityType:data.entityType }; this.router.navigate(['observation/details'], { diff --git a/src/app/client/src/app/modules/player-helper/components/contentplayer-page/contentplayer-page.component.ts b/src/app/client/src/app/modules/player-helper/components/contentplayer-page/contentplayer-page.component.ts index fcc845d3063..10c15ecfa65 100644 --- a/src/app/client/src/app/modules/player-helper/components/contentplayer-page/contentplayer-page.component.ts +++ b/src/app/client/src/app/modules/player-helper/components/contentplayer-page/contentplayer-page.component.ts @@ -118,7 +118,7 @@ export class ContentPlayerPageComponent implements OnInit, OnDestroy, OnChanges this.playerService.getContent(this.contentId, params) .pipe(takeUntil(this.unsubscribe$)) .subscribe(response => { - this.contentDetails = _.get(response, 'result.content') || _.get(response, 'result.questionset'); + this.contentDetails = _.get(response, 'result.content') || _.get(response, 'result.questionset') || _.get(response, 'result.questionSet'); const status = !_.has(this.contentDetails, 'desktopAppMetadata.isAvailable') ? false : !_.get(this.contentDetails, 'desktopAppMetadata.isAvailable'); this.isContentDeleted.next({value: status}); diff --git a/src/app/client/src/app/modules/player-helper/components/player/player.component.scss b/src/app/client/src/app/modules/player-helper/components/player/player.component.scss index 0e2ff2c9cf2..95f8c9e434a 100644 --- a/src/app/client/src/app/modules/player-helper/components/player/player.component.scss +++ b/src/app/client/src/app/modules/player-helper/components/player/player.component.scss @@ -132,3 +132,6 @@ overflow: hidden; position: relative; } +.contentViewerIframeShadow.player-fullscreen .custom-web-player{ + height: calc(100vh - 4rem); +} \ No newline at end of file diff --git a/src/app/client/src/app/modules/player-helper/components/player/player.component.spec.ts b/src/app/client/src/app/modules/player-helper/components/player/player.component.spec.ts index 444dd42be52..1664407d04c 100644 --- a/src/app/client/src/app/modules/player-helper/components/player/player.component.spec.ts +++ b/src/app/client/src/app/modules/player-helper/components/player/player.component.spec.ts @@ -1008,7 +1008,7 @@ describe('PlayerComponent', () => { jest.useFakeTimers(); const spy = jest.spyOn(component, 'videoPlayerConfig'); component.ngAfterViewInit(); - jest.advanceTimersByTime(200); + jest.advanceTimersByTime(500); expect(spy).toHaveBeenCalled(); }); @@ -1017,7 +1017,7 @@ describe('PlayerComponent', () => { component.playerType = "audio-player"; jest.useFakeTimers(); component.ngAfterViewInit(); - jest.advanceTimersByTime(200); + jest.advanceTimersByTime(500); expect(playerService.getQuestionSetRead).not.toHaveBeenCalled(); }); @@ -1038,7 +1038,7 @@ describe('PlayerComponent', () => { jest.useFakeTimers(); const spy = jest.spyOn(component, 'pdfPlayerConfig'); component.ngAfterViewInit(); - jest.advanceTimersByTime(200); + jest.advanceTimersByTime(500); expect(spy).toHaveBeenCalled(); }); it('should call epubPlayerConfig after 200ms if playerType is "epub-player"', () => { @@ -1058,7 +1058,7 @@ describe('PlayerComponent', () => { jest.useFakeTimers(); const spy = jest.spyOn(component, 'epubPlayerConfig'); component.ngAfterViewInit(); - jest.advanceTimersByTime(200); + jest.advanceTimersByTime(500); expect(spy).toHaveBeenCalled(); }); @@ -1079,7 +1079,7 @@ describe('PlayerComponent', () => { jest.useFakeTimers(); const spy = jest.spyOn(component, 'qumlPlayerConfig'); component.ngAfterViewInit(); - jest.advanceTimersByTime(200); + jest.advanceTimersByTime(500); expect(spy).toHaveBeenCalled(); }); it('should not call any player config method for unknown playerType', () => { diff --git a/src/app/client/src/app/modules/program-dashboard/components/program-datasets/program-datasets.component.html b/src/app/client/src/app/modules/program-dashboard/components/program-datasets/program-datasets.component.html index ea8533a6cca..9b15f8ae670 100644 --- a/src/app/client/src/app/modules/program-dashboard/components/program-datasets/program-datasets.component.html +++ b/src/app/client/src/app/modules/program-dashboard/components/program-datasets/program-datasets.component.html @@ -56,26 +56,25 @@

{{reso -
-
-
+ +
+
- - - {{resourceService?.frmelmnts?.lbl?.programLbl}} + + - - {{program.name}} - + [placeholder]="resourceService?.frmelmnts?.lbl?.program" (selectionChange)="programSelection($event)"> + {{program.name}} - +
- - + {{resourceService?.frmelmnts?.lbl?.solutionLbl}} + + [placeholder]="resourceService?.frmelmnts?.lbl?.solution" (selectionChange)="selectSolution($event)"> {{solution.name}} @@ -83,8 +82,8 @@

{{reso

- - + {{ resourceService?.frmelmnts?.lbl?.dashboarddistrictLbl }} + @@ -94,8 +93,8 @@

{{reso

- - + {{ resourceService?.frmelmnts?.lbl?.block }} + @@ -105,8 +104,8 @@

{{reso

- - + {{ resourceService?.frmelmnts?.lbl?.dashboardOrgLbl }} + @@ -116,22 +115,22 @@

{{reso

-
- - +
+ {{resourceService?.frmelmnts?.lbl?.startdate | titlecase }} + - + *{{this.resourceService?.frmelmnts?.msg?.invalidDateMsg | interpolate:'{type}': (resourceService?.frmelmnts?.lbl?.startdate | lowercase)}}
-
- - +
+ {{resourceService?.frmelmnts?.lbl?.enddate | titlecase }} + - + @@ -143,12 +142,10 @@

{{reso - -

-
- - +
+ + {{option}} @@ -169,7 +166,7 @@

{{reso
*{{errorMessage}}
-
+
@@ -178,10 +175,10 @@

{{reso
- + - + {{report?.name}} @@ -215,7 +212,7 @@

{{reso
*{{errorMessage}}
-
@@ -266,143 +263,119 @@

{{reso - + -
-
- + +
+
+ {{resourceService?.frmelmnts?.lbl?.confirmReportRequest}} +
+ +
+ +
+
+ + + +

+ {{resourceService?.frmelmnts?.lbl?.pswdRule}} +

+
+
+ +
- -
-
- - -
-
-