Creating Better PCF Component – Part 2

When I was writing this post, I felt the environment that I set up did not enhance my ability to write code. The reason for this is because I use jsdom, which is a mock object for the HTML component. Because it is not a real object of HTML, my focus has been changed to the test code instead of writing the implementation code. 

I ask my friend about how it is hard to implement unit testing in PCF Development (I need to install various NPM Packages to do the testing only. Most of the time because standard HTML API is not there!). Then he replied that Node.Js is not the same as Javascript in HTML. It means that PCF runs in Node.js for development, but we rely on browsers instead of Node.js functions. So my focus is set once again to make it better. We will use karma for running our tests. If you don’t understand the context here, you can refer to this blog-post for more detail.

Setup NPM Packages

You only need to install all these packages below in your PCF Project:

npm i --save-dev chai @types/chai mocha @types/mocha karma karma-chai karma-chrome-launcher karma-cli karma-firefox-launcher karma-mocha karma-typescript mocha

Config Karma + tsconfig.json and package.json

Create karma.conf.js in your root folder of your project. Then set up the code like this:

module.exports = function (config) {
    config.set({
        frameworks: ["mocha", "chai", "karma-typescript"],
        files: ["ImageFromString/**/*.ts"],
        preprocessors: {
            "ImageFromString/**/*.ts": ["karma-typescript"]
        },
        colors: true,
        ports: 9876,
        karmaTypescriptConfig: {
            tsconfig: "./tsconfig.json"
        },
        logLevel: config.LOG_INFO,
        autoWatch: false,
        reporters: ["karma-typescript", "progress"],
        browsers: ["FirefoxHeadless", "ChromeDebugging"],
        customLaunchers: {
            'ChromeDebugging': {
                base: 'Chrome',
                flags: [
                    '--remote-debugging-port=9222',
                    '--inspect',
                    '--single-run:false'
                ],
                debug: true
            },
            'FirefoxHeadless': {
                base: 'Firefox',
                flags: [
                    '-headless',
                ],
                prefs: {
                    'network.proxy.type': 0
                }
            },
        },
        concurrecy: Infinity
    });
};

In the above configuration, we set karma to load mocha, chai, and karma-typescript as our framework. The pattern of the files that we will load is all files with extension .ts in folder ImageFromString (my component folder). Then all the .ts files will run karma-typescript. We also set-up 2 browsers (chrome and firefox to show you that we can have both). Firefox is our default testing, while chrome for debugging purposes (I still find a way to able debug it perfectly).

Then here is our tsconfig.json file:

{
    "extends": "./node_modules/pcf-scripts/tsconfig_base.json",
    "compileOnSave": false,
    "compilerOptions": {
        "lib": [
            "es5",
            "es6",
            "dom"
        ],
        "module": "commonjs",
        "noImplicitAny": true,
        "outDir": "tmp",
        "target": "es5",
        "sourceMap": true,
        "typeRoots": [
            "node_modules/@types"
        ]
    },
    "include": [
        "./ImageFromString/**/*"
    ],
    "exclude": [
        "node_modules"
    ]
}

For package.json, we set it like this:

{
    "name": "pcf-project",
    "version": "1.0.0",
    "description": "Project containing your PowerApps Component Framework (PCF) control.",
    "scripts": {
        "build": "pcf-scripts build",
        "clean": "pcf-scripts clean",
        "rebuild": "pcf-scripts rebuild",
        "start": "pcf-scripts start",
        "test": "karma start karma.conf.js --browsers=FirefoxHeadless --single-run",
        "debug": "karma start karma.conf.js --browsers=ChromeDebugging"
    },
    "dependencies": {
        "@types/node": "^10.12.18",
        "@types/powerapps-component-framework": "^1.2.0"
    },
    "devDependencies": {
        "@types/chai": "^4.2.14",
        "@types/mocha": "^8.0.4",
        "chai": "^4.2.0",
        "karma": "^5.2.3",
        "karma-chai": "^0.1.0",
        "karma-chrome-launcher": "^3.1.0",
        "karma-cli": "^2.0.0",
        "karma-firefox-launcher": "^2.1.0",
        "karma-mocha": "^2.0.1",
        "karma-typescript": "^5.2.0",
        "mocha": "^8.2.1",
        "pcf-scripts": "^1",
        "pcf-start": "^1",
        "ts-node": "^9.0.0",
        "typescript": "^4.0.5"
    }
}

Flag –single-run to command karma to run our tests directly after it loaded.

Sample of the Tests

Because now we rely on the real function on browsers. Now our test can be more simpler:

import { expect } from 'chai';
import { fileUpload, controls } from './file-upload';
import { IInputs } from './generated/ManifestTypes';

describe('fileupload control tests', () => {
  let context: ComponentFramework.Context<IInputs>;
  let htmlDivElement: HTMLDivElement;

  beforeEach(() => {
    htmlDivElement = document.createElement('div') as HTMLDivElement;
    document.body.appendChild(htmlDivElement);
    
    context = {} as ComponentFramework.Context<IInputs>;
  });

  describe('generate fileupload', () => {
    it('can generate fileupload control', () => {
      const file = new fileUpload(context, htmlDivElement, () => { });
      file.generate();

      expect(file).not.null;

      const div = document.getElementById(controls.div);
      expect(div).not.null;

      const label = document.getElementById(controls.label);
      expect(label).not.null;

      const imageUpload = document.getElementById(controls.file);
      expect(imageUpload).not.null;

      const button = document.getElementById(controls.button);
      expect(button).not.null;

      const information = document.getElementById(controls.information);
      expect(information).not.null;
    });

    it('can validate control', () => {
      const file = new fileUpload(context, htmlDivElement, () => { });
      file.generate();
      file.submit();

      const information = document.getElementById(controls.information);
      expect(information?.style.display).to.equal('block');
    });

    it('can transform Blob to Base64String', async () => {
      const blob = new Blob(['a'.repeat(1024)], {type: 'image/jpg'});
      const file = new fileUpload(context, htmlDivElement, () => { });
      const result = await file.convertToBase64(blob);
      expect(result).to.not.null;
    })
  });
});

Running Our Tests

For running our test, we just need to run this command:

npm run test

And here is the result:

Summary

  • We removed various NPM Packages from last blog post (jsdom, @types/jsdom).
  • We removed /.setup-test.ts because now we rely on the real HTML object.
  • Setup karma, mocha, and chai and changed how we call npm run test.
  • Changed of the unit tests classes.
  • All of these changes is for testing only.

You can refer the full code on this blog post series in here.

Creating Better PCF Component Series:

6 thoughts on “Creating Better PCF Component – Part 2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.