Commit 250edb2e authored by Laurent Sittler's avatar Laurent Sittler ©
Browse files

feat: add code coverage

parents b8fed1af aa7f680b
Pipeline #964 passed with stages
in 5 minutes and 8 seconds
image: node:10
stages:
- coverage
- build
- package
- setup
- build
- test
- package
test:
image: node:10
stage: coverage
npm:
stage: setup
only:
- [email protected]/sp-dev-fx-webparts/scrollToTop
- merge_requests
except:
variables:
- $CI_COMMIT_MESSAGE =~ /Update Changelog from CI/
script:
- npm i
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
extension:
stage: build
only:
- [email protected]/sp-dev-fx-webparts/scrollToTop
- merge_requests
script:
- npm run build
- mv ./sharepoint/solution/scrollToTop.sppkg ././scrollToTop.sppkg
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull
artifacts:
paths:
- scrollToTop.sppkg
allow_failure: false
coverage:
stage: test
only:
- [email protected]/sp-dev-fx-webparts/scrollToTop
- merge_requests
except:
variables:
- $CI_COMMIT_MESSAGE =~ /Update Changelog from CI/
script:
- npm test
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull
artifacts:
paths:
- jest
......@@ -16,69 +61,57 @@ test:
cobertura: jest/cobertura-coverage.xml
junit: junit.xml
extension:
image: node:10
stage: build
only:
- tags
script:
- npm i -g gulp
- npm i
- npm audit fix
- gulp bundle --ship
- gulp package-solution --ship
- mv ./sharepoint/solution/scrollToTop.sppkg ././scrollToTop.sppkg
artifacts:
paths:
- scrollToTop.sppkg
allow_failure: false
bash:
image: node:latest
stage: package
dependencies:
- extension
only:
- tags
script:
- apt-get update -y && apt-get install p7zip-full -y
- 7z a scrollToTop-setup-bash.zip *.sh
- 7z a scrollToTop-setup-bash.zip scrollToTop.sppkg
artifacts:
paths:
- scrollToTop-setup-bash.zip
allow_failure: false
image: node:latest
stage: package
dependencies:
- extension
only:
- tags
except:
variables:
- $CI_COMMIT_MESSAGE =~ /Update Changelog from CI/
script:
- apt-get update -y && apt-get install p7zip-full -y
- 7z a scrollToTop-setup-bash.zip *.sh
- 7z a scrollToTop-setup-bash.zip scrollToTop.sppkg
artifacts:
paths:
- scrollToTop-setup-bash.zip
allow_failure: false
powershell:
image: node:latest
stage: package
dependencies:
- extension
only:
- tags
script:
- apt-get update -y && apt-get install p7zip-full -y
- 7z a scrollToTop-setup-powershell.zip *.ps1
- 7z a scrollToTop-setup-powershell.zip scrollToTop.sppkg
artifacts:
paths:
- scrollToTop-setup-powershell.zip
allow_failure: false
image: node:latest
stage: package
dependencies:
- extension
only:
- tags
except:
variables:
- $CI_COMMIT_MESSAGE =~ /Update Changelog from CI/
script:
- apt-get update -y && apt-get install p7zip-full -y
- 7z a scrollToTop-setup-powershell.zip *.ps1
- 7z a scrollToTop-setup-powershell.zip scrollToTop.sppkg
artifacts:
paths:
- scrollToTop-setup-powershell.zip
allow_failure: false
changelog:
image: node:latest
stage: package
only:
- master
except:
variables:
- $CI_COMMIT_MESSAGE =~ /Update Changelog from CI/
script:
- npm install -g conventional-changelog-cli && npm install -g version && apt-get install git
- mkdir repo && cd ./repo
- git clone -b $CI_COMMIT_REF_NAME https://gitlab-runner:[email protected]gitlab.lsonline.fr/$CI_PROJECT_PATH.git && git init &> /dev/null
- cd ./$CI_PROJECT_NAME
- conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md && git pull
- git config --global user.email "$GITLAB_USER_EMAIL"
- git commit -m 'Update Changelog from CI' && git push origin $CI_COMMIT_REF_NAME &> /dev/null
image: node:latest
stage: package
only:
- master@SharePoint/sp-dev-fx-webparts/scrollToTop
except:
variables:
- $CI_COMMIT_MESSAGE =~ /Update Changelog from CI/
script:
- npm install -g conventional-changelog-cli && npm install -g version && apt-get install git
- mkdir repo && cd ./repo
- git clone -b $CI_COMMIT_REF_NAME https://gitlab-runner:[email protected]$CI_SERVER_HOST/$CI_PROJECT_PATH.git && git init &> /dev/null
- cd ./$CI_PROJECT_NAME
- conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md && git pull
- git config --global user.email "$GITLAB_USER_EMAIL"
- git commit -m 'Update Changelog from CI' && git push origin $CI_COMMIT_REF_NAME &> /dev/null
......@@ -25,7 +25,7 @@
"name": "Laurent Sittler",
"privacyUrl": "",
"termsOfUseUrl": "",
"websiteUrl": "hhttps://gitlab.lsonline.fr/SharePoint/sp-dev-fx-webparts/scrollToTop",
"websiteUrl": "https://gitlab.lsonline.fr/SharePoint/sp-dev-fx-webparts/scrollToTop",
"mpnId": ""
}
},
......
declare module "*.scss" {
const content: { [className: string]: string };
export = content;
}
\ No newline at end of file
}
\ No newline at end of file
......@@ -7,7 +7,7 @@
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"build": "gulp bundle --ship && gulp package-solution --ship",
"clean": "gulp clean",
"test": "jest"
},
......
......@@ -12,8 +12,7 @@ import { IScrollToTopButton } from './components/IScrollToTopButtonApplicationCu
import styles from './components/ScrollToTopApplicationCustomizer.module.scss';
import * as strings from 'ScrollToTopApplicationCustomizerStrings';
const scrollRegions: any = require('./ScrollToTopApplicationRegions.json');
import { Scroll } from './models/Scroll';
const LOG_SOURCE: string = 'ScrollToTopApplicationCustomizer';
......@@ -34,47 +33,18 @@ export interface IScrollToTopApplicationCustomizerProperties {
export default class ScrollToTopApplicationCustomizer extends BaseApplicationCustomizer<IScrollToTopApplicationCustomizerProperties> {
//#region Properties
/** Content Page overflow
* @property
* @private
*/
private _scrollRegion: HTMLElement = undefined;
/** SharePoint Modern bottom area
* @property
* @private
*/
private _bottomPlaceholder: PlaceholderContent | undefined;
/** Statement of smooth scroll
* @property
* @private
*/
private _currentTime: number = 0;
/** Timeout execution of scroll
* @property
* @private
*/
private _increment: number = 20;
/** Distance beetween the top of the page and the current position
* @property
* @private
*/
private _change: number = 0;
/** Current position in the page
* @property
* @private
/**
* Scroll manager
*/
private _start: number = 0;
private _scroll: Scroll;
/** Mobile view determined by class name (currently unused)
* @property
* @private
*/
private _isMobileView: boolean = false;
private _element: React.ReactElement<IScrollToTopButton>;
//#endregion
@override
......@@ -96,78 +66,36 @@ export default class ScrollToTopApplicationCustomizer extends BaseApplicationCus
this.properties.shape = 'square';
}
this._scroll = new Scroll(this.properties.scrollDuration);
this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
this.context.application.navigatedEvent.add(this, this._renderPlaceHolders);
return Promise.resolve();
}
/** Get the scroll region in accordance with the defined scroll regions
* @see ScrollToTopApplicationRegions
* @param i Object index for recursive function
* @private
*/
private getScrollRegion(i: number = 0) {
this._scrollRegion = document.querySelector(`${scrollRegions[i].selector}`);
if (!this._scrollRegion) {
if (i < scrollRegions.length) {
this.getScrollRegion((i + 1));
} else {
console.log('No scroll region found.');
}
} else {
this._isMobileView = scrollRegions[i].isMobile;
if (scrollRegions[i].forceOverflow) {
this._scrollRegion.style.overflow = 'auto';
}
}
}
/** Launch scroll to top
* @description Original animatedScrollTo come from: https://gist.github.com/andjosh/6764939
* @private
*/
private scrollTo = () => {
this._scrollRegion = undefined;
this._isMobileView = false;
this.getScrollRegion();
if (this._scrollRegion) {
this._start = this._scrollRegion.scrollTop;
this._change = 0 - this._start;
this._currentTime = 0;
this.animateScroll();
}
}
/** Add a smoothly scroll effect to top
* @private
*/
private animateScroll() {
this._currentTime += this._increment;
var val = this.easeInOutQuad(this._currentTime, this._start, this._change, this.properties.scrollDuration);
this._scrollRegion.scrollTop = val;
if (this._currentTime < this.properties.scrollDuration) {
setTimeout(() => {
this.animateScroll();
}, this._increment);
this._scroll.scrollRegion = undefined;
this._scroll.isMobileView = false;
try {
this._scroll.getScrollRegion();
if (this._scroll.scrollRegion) {
this._scroll.start = this._scroll.scrollRegion.scrollTop;
this._scroll.change = 0 - this._scroll.start;
this._scroll.currentTime = 0;
this._scroll.animateScroll();
}
} catch (ex) {
console.log('No scroll region found.', ex);
}
}
/** Calc the effect to easy In/Out scroll
* @param t Current time
* @param b Start from page position
* @param c Distance between the start and the top of the page
* @param d Duration of scroll
* @private
*/
private easeInOutQuad(t: number, b: number, c: number, d: number): number {
t /= d / 2;
if (t < 1) return c / 2 * t * t + b;
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
}
/** Use place holder of SharePoint
* @private
*/
......@@ -185,7 +113,7 @@ export default class ScrollToTopApplicationCustomizer extends BaseApplicationCus
}
let e: HTMLCollectionOf<Element> = document.getElementsByClassName(`.${styles.spfxScrolltotopBtn}`);
if (this._bottomPlaceholder.domElement && (!e || e.length < 1)) {
const element: React.ReactElement<IScrollToTopButton> = React.createElement(
this._element = React.createElement(
ScrollToTopButton,
{
icon: this.properties.buttonIcon,
......@@ -195,7 +123,7 @@ export default class ScrollToTopApplicationCustomizer extends BaseApplicationCus
clickHandler: this.scrollTo
}
);
ReactDom.render(element, this._bottomPlaceholder.domElement);
ReactDom.render(this._element, this._bottomPlaceholder.domElement);
}
}
}
......@@ -205,5 +133,6 @@ export default class ScrollToTopApplicationCustomizer extends BaseApplicationCus
* @private
*/
private _onDispose(): void {
console.log('[ScrollToTopApplicationCustomizer._onDispose] Disposed custom top and bottom placeholders.');
}
}
......@@ -14,7 +14,7 @@ import { ScrollToTopButton } from './ScrollToTopApplicationCustomizer';
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';
initializeIcons();
describe('Scroll to Top', () => {
describe('React Scroll to Top', () => {
let reactComponent: ReactWrapper<IScrollToTopButton>;
beforeEach(() => {
......
/// <reference types="jest" />
import { Scroll } from './Scroll';
describe('Scroll Model', () => {
let myScroll: Scroll;
afterEach(() => {
const elem = document.querySelector("[data-is-scrollable='true']");
if (elem) {
elem.remove();
}
});
it('should create a new Scroll model', () => {
myScroll = new Scroll(1000);
expect(myScroll).toEqual({
_currentTime: 0,
_increment: 20,
_change: 0,
_isMobileView: false,
_scrollRegion: undefined,
_duration: 1000,
_start: 0
});
});
describe('Getters/Setters', () => {
beforeEach(() => {
myScroll = new Scroll(1000);
});
it('should set myScroll._change to the passed argument \'500\'', () => {
myScroll.change = 500;
expect(myScroll).toEqual({
_currentTime: 0,
_increment: 20,
_change: 500,
_isMobileView: false,
_scrollRegion: undefined,
_duration: 1000,
_start: 0
});
});
it('should return the \'change\' number when the getter \'change\' called', () => {
myScroll.change = 200;
expect(myScroll.change).toEqual(200);
});
it('should set myScroll._currentTime to the passed argument \'100\'', () => {
myScroll.currentTime = 100;
expect(myScroll).toEqual({
_currentTime: 100,
_increment: 20,
_change: 0,
_isMobileView: false,
_scrollRegion: undefined,
_duration: 1000,
_start: 0
});
});
it('should return the \'currentTime\' number when the getter \'currentTime\' called', () => {
myScroll.currentTime = 200;
expect(myScroll.currentTime).toEqual(200);
});
it('should set myScroll._duration to the passed argument \'3000\'', () => {
myScroll.duration = 3000;
expect(myScroll).toEqual({
_currentTime: 0,
_increment: 20,
_change: 0,
_isMobileView: false,
_scrollRegion: undefined,
_duration: 3000,
_start: 0
});
});
it('should return the \'duration\' number when the getter \'duration\' called', () => {
expect(myScroll.duration).toEqual(1000);
});
it('should set myScroll._isMobileView to the passed argument \'true\'', () => {
myScroll.isMobileView = true;
expect(myScroll).toEqual({
_currentTime: 0,
_increment: 20,
_change: 0,
_isMobileView: true,
_scrollRegion: undefined,
_duration: 1000,
_start: 0
});
});
it('should return the \'isMobileView\' boolean when the getter \'isMobileView\' called', () => {
expect(myScroll.isMobileView).toEqual(false);
});
it('should set myScroll._scrollRegion to the passed argument \'true\'', () => {
document.body.innerHTML = '<div data-is-scrollable="true" class="a_a_beed2cf1 c_a_beed2cf1" data-automation-id="contentScrollRegion" role="main" tabindex="-1" data-sp-component-lazycount="0"><div><div id="spPageCanvasContent">Page Content</div></div></div>';
const region: HTMLElement = document.querySelector("[data-automation-id='contentScrollRegion']");
myScroll.scrollRegion = region;
expect(myScroll).toEqual({
_currentTime: 0,
_increment: 20,
_change: 0,
_isMobileView: false,
_scrollRegion: region,
_duration: 1000,
_start: 0
});
});
it('should return the \'scrollRegion\' HTMLElement when the getter \'scrollRegion\' called', () => {
expect(myScroll.scrollRegion).toEqual(undefined);
});
it('should set myScroll._start to the passed argument \'50\'', () => {
myScroll.start = 50;
expect(myScroll).toEqual({
_currentTime: 0,
_increment: 20,
_change: 0,
_isMobileView: false,
_scrollRegion: undefined,
_duration: 1000,
_start: 50
});
});
it('should return the \'start\' number when the getter \'start\' called', () => {
expect(myScroll.start).toEqual(0);
});
});
describe('Get Scroll region', () => {
beforeEach(() => {
myScroll = new Scroll(1000);
});
it('should retrieve the \'scrollRegion\' HTMLElement from the HTML body in Desktop mode when \'getScrollRegion\' called', () => {
document.body.innerHTML = '<div data-is-scrollable="true" class="a_a_beed2cf1 c_a_beed2cf1" data-automation-id="contentScrollRegion" role="main" tabindex="-1" data-sp-component-lazycount="0"><div><div id="spPageCanvasContent">Page Content</div></div></div>';
myScroll.getScrollRegion();
const region: HTMLElement = document.querySelector("[data-automation-id='contentScrollRegion']");
expect(myScroll.scrollRegion).toEqual(region);
});
it('should retrieve the \'scrollRegion\' HTMLElement from the HTML body in Mobile mode, then update \'isMobileView\' when \'getScrollRegion\' called', () => {
document.body.innerHTML = '<div data-is-scrollable="true" class="pageLayout_255" role="main" tabindex="-1" data-sp-component-lazycount="0"><div><div id="spPageCanvasContent">Page Content</div></div></div>';
myScroll.getScrollRegion();
const region: HTMLElement = document.querySelector("[class^=pageLayout_]");
expect(myScroll.scrollRegion).toEqual(region);
expect(myScroll.isMobileView).toEqual(true);
});
it('should retrieve and add attribute to the \'scrollRegion\' HTMLElement from the HTML body in Mobile mode when \'getScrollRegion\' called', () => {
document.body.innerHTML = '<div data-is-scrollable="true" class="pageLayout_255" role="main" tabindex="-1" data-sp-component-lazycount="0"><div><div id="spPageCanvasContent">Page Content</div></div></div>';
myScroll.getScrollRegion();
const region: HTMLElement = document.querySelector("[class^=pageLayout_]");
expect(region.style.overflow).toEqual('auto');
});
it('should throw an error when \'getScrollRegion\' is called an no region was found', () => {
expect(() => {
myScroll.getScrollRegion();
}).toThrow(TypeError);
});
it('should throw an error when \'getScrollRegion\' is called with an index out of range', () => {
expect(() => {
myScroll.getScrollRegion(99);
}).toThrow(TypeError);
});
});
describe('Get Scroll region', () => {
beforeEach(() => {
myScroll = new Scroll(1000);
});
it('should scroll to the top of the page with the specified duration when \'animateScroll\' called', (done) => {
document.body.innerHTML = '<div data-is-scrollable="true" class="a_a_beed2cf1 c_a_beed2cf1" data-automation-id="contentScrollRegion" role="main" tabindex="-1" data-sp-component-lazycount="0"><div><div id="spPageCanvasContent">Page Content</div></div></div>';
const region: HTMLElement = document.querySelector("[data-automation-id='contentScrollRegion']");
region.scrollTop = 500;
myScroll.getScrollRegion();
expect(myScroll.scrollRegion.scrollTop).toEqual(500);
myScroll.animateScroll();