Skip to main content

Jest - Issues

· One min read

Open in Notion

Configurations for DOM Support

Use document object and methods like document.querySelectorAll

_setup.ts

import "jsdom-global/register";

jest.config.js

module.exports = {
verbose: true,
transform: {
".(ts|tsx)": "<rootdir>/node_modules/ts-jest/preprocessor.js",
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub",
},
testEnvironment: "node",
testRegex: "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
setupTestFrameworkScriptFile: "./test/_setup.ts",
moduleFileExtensions: ["ts", "tsx", "js"],
coveragePathIgnorePatterns: ["/node_modules/", "/test/", "src/index.ts"],
coverageThreshold: {
global: {
branches: 0,
functions: 0,
lines: 0,
statements: 0,
},
},
collectCoverageFrom: ["src/**/*.{js,ts,tsx}"],
};

UT Example for Promise

// ut passed requires `done` called
it("should return a promise with callback and title", done => {
confirm("Promise confirm").then(() =&gt; {
done();
});
document.querySelectorAll<htmlelement>(".btn")[1].click();
});

// reject promise
it("test reject promise", async () => {
// do not use `Promise.reject`, because returns Promise immediately
const mockP = jest.fn(() => Promise.reject("err"));
await delay(1, mockP()).catch(() => {
// nothing
});
expect(mockP.mock.calls.length).toBe(1);
});

Microsoft Terminal

· One min read

Open in Notion

Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Set-ExecutionPolicy -Scope CurrentUser Bypass

oh-my-posh

Install from local

  1. Download the nupkg file from https://www.powershellgallery.com/packages?q=oh-my-posh

  2. Run below command to register repository and install

    Register-PSRepository -Name 'oh-my-posh' -SourceLocation 'your-nupkg-folder'
    Install-Module oh-my-posh -Scope CurrentUser -Repository oh-my-posh
    Import-Module oh-my-posh

Commands

Get-PoshThemes
Get-PoshThemes -list

AMD、CMD、UMD、CommonJS

· 3 min read

Open in Notion

Asynchronous Module Definition (AMD) has gained traction on the frontend, with RequireJS being the most popular implementation.

Here’s module foo with a single dependency on jquery:

// filename: foo.js
define(['jquery'], function ($) {
// methods
function myFunc(){};

// exposed public methods
return myFunc;
});

And a little more complicated example with multiple dependencies and multiple exposed methods:

// filename: foo.js
define(['jquery', 'underscore'], function ($, _) {
// methods
function a(){}; // private because it's not returned (see below)
function b(){}; // public because it's returned
function c(){}; // public because it's returned

// exposed public methods
return {
b: b,
c: c
}
});

CMD(Common Module Definition)

Standard locates at https://github.com/seajs/seajs/issues/242. It keeps more compatibilities with CommonJS and Node.js Modules.

  • Published by Chinese people who is developing SeaJS.
  • It is like AMD.
define((require, exports, module) => {
module.exports = {
fun1: () => {
var $ = require('jquery');
return $('#test');
}
};
});

CommonJS

CommonJS is a style you may be familiar with if you’re written anything in Node (which uses a slight variant). It’s also been gaining traction on the frontend with Browserify.

Using the same format as before, here’s what our foo module looks like in CommonJS:

// filename: foo.js

// dependencies
var $ = require('jquery');

// methods
function myFunc(){};

// exposed public method (single)
module.exports = myFunc;

And our more complicate example, with multiple dependencies and multiple exposed methods:

// filename: foo.js
var $ = require('jquery');
var _ = require('underscore');

// methods
function a(){}; // private because it's omitted from module.exports (see below)
function b(){}; // public because it's defined in module.exports
function c(){}; // public because it's defined in module.exports

// exposed public methods
module.exports = {
b: b,
c: c
};

UMD(Universal Module Definition)

Since CommonJS and AMD styles have both been equally popular, it seems there’s yet no consensus. This has brought about the push for a “universal” pattern that supports both styles, which brings us to none other than the Universal Module Definition.

The pattern is admittedly ugly, but is both AMD and CommonJS compatible, as well as supporting the old-style “global” variable definition:

(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// methods
function myFunc(){};

// exposed public method
return myFunc;
}));

And keeping in the same pattern as the above examples, the more complicated case with multiple dependencies and multiple exposed methods:

(function (root, factory) {
if (typeof define === 'function' &amp;&amp; define.amd) {
// AMD
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'), require('underscore'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// methods
function a(){}; // private because it's not returned (see below)
function b(){}; // public because it's returned
function c(){}; // public because it's returned

// exposed public methods
return {
b: b,
c: c
}
}));

AngularJS with TypeScript

· One min read

Open in Notion

Components

class HerosComponentController implements ng.IComponentController {
public static $inject = ['$log', '$scope', '$document', '$element'];
public title: string;
public heros: IHero[];
public onItemSelect: any;

constructor(
private $log: ng.ILogService,
private $scope: ng.IScope,
private $document: ng.IDocumentService,
private $element: ng.IRootElementService,
) { }

public $onInit () { }

public $onChanges(changes: angular.IOnChangesObject): void { }

public selectItem(item: IHero, event: any) {
if (this.onItemSelect && typeof this.onItemSelect === 'function') {
this.onItemSelect({
data: item,
});
}
}
}

class HerosComponent implements ng.IComponentOptions {

public controller: ng.Injectable<ng.IControllerConstructor>;
public controllerAs: string;
public template: string;
public bindings: any;

constructor() {
this.controller = HerosComponentController;
this.controllerAs = "$ctrl";
this.template = `
<ul>
<li>{{$ctrl.title}}</li>
<li ng-click="$ctrl.selectItem(hero, $event)" ng-repeat="hero in $ctrl.heros">{{ hero.name }}</li>
</ul>
`;
this.bindins = {
title: '@',
heros: '<',
onItemSelect: '&',
};
}
}

angular
.module("mySuperAwesomeApp", [])
.component("heros", new HerosComponent());

angular.element(document).ready(function() {
angular.bootstrap(document, ["mySuperAwesomeApp"]);
});
<heros title="Title" heros="$ctrl.heros" on-item-select="$ctrl.select(data)"></heros>

AOP – Aspect-oriented programming

· 4 min read

Open in Notion

It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a “pointcut” specification, such as “log all function calls when the function’s name begins with ‘set’”. This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code, core to the functionality. AOP forms a basis for aspect-oriented software development.

Define an Aspect by using @Aspect annotations – which is natively understood by Spring:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class AuditAspect {

private static Logger logger = LoggerFactory.getLogger(AuditAspect.class);

@Pointcut("execution(* net.bndy.service.*.*(..))")
public void serviceMethods(JoinPoint jp){ }

// Intercept all calls to methods annotated with @PerLog
// @Pointcut("execution(@net.bndy.annotations.PerfLog * *.*(..))")
public void performanceTargets(JoinPoint jp, PerLog perLog){ }
// Pass annotation to AOP advices
@Pointcut(value = "@annotation(perLog)", argNames = "perfLog")
public void performanceTargets(JoinPoint jp, PerLog perLog){ }

// Using annotations and parameters in AOP advices
@Before(value = "com.byteslounge.spring.aop.PointcutDefinition.serviceLayer() && args(account,..) && @annotation(auditable)")
public void audit(Account account, Auditable auditable) {
System.out.println("Audit account access: "
+ account.getAccountNumber() + ". Audit destination: "
+ auditable.value());
}

// If the first parameter is of the JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart type, you may leave out the name of the parameter from the value of the "argNames" attribute. For example, if you modify the preceding advice to receive the join point object, the "argNames" attribute need not include it:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}

@Before("serviceMethods()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
logger.info("before method:" + methodName);
}

@Around("serviceMethods()")
public Object aroundMethod(ProceedingJoinPoint jp) {
try {
long start = System.nanoTime();
// execute target method
Object result = jp.proceed();
long end = System.nanoTime();
logger.info(String.format("%s took %d ns", jp.getSignature(), (end - start)));
return result;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}

// Always run if method completed
@After("serviceMethods()")
public void afterMethod(JoinPoint jp) {
logger.info("after method");
}

// Run when the method returns normally.
@AfterReturning(value="execution(* net.bndy.service.*.*(..))",returning="result")
public void afterReturningMethod(JoinPoint jp, Object result){ }

// Run if an exception has benn thrown in method
@AfterThrowing(value="execution(* net.bndy.service.*.*(..))",throwing="e")
public void afterThorwingMethod(JoinPoint jp, NullPointerException e){ }
}

@Pointcut

// Any return type | package | class | method | any type and number of arguments
@Pointcut("execution(* net.bndy.service . *. *( .. ))")
@Pointcut("execution(* net.bndy.service.*.*(.. ))")

// examples
@Pointcut("within(*..*Test)") // support for sub packages is provided with ".."
@Pointcut("within(net.bndy.service..*Test)") // ends with Test inside the "net.bndy.service" package
@Pointcut("!within(net.bndy.service..*Test)" // expects that ends with Test inside the "net.bndy.service" package
@Pointcut("execution(void *..service.Service+.*(..))") // all methods in the Service class or a subtype of it

@Pointcut("!withincode(@org.junit.Test * *(..))") //Finds all statements that’s not inside a method marked with @Test

// other Pointcuts
//execution(void Point.setX(int))
//call(void Point.setX(int))
//handler(ArrayOutOfBoundsException)
//this(SomeType)
//target(SomeType)
//within(MyClass)
//cflow(call(void Test.main()))
//call(* *(..)) &amp;&amp; (within(Line) || within(Point))
//execution(!static * *(..))
//execution(public !static * *(..))

Why to use argNames

public class HelloApi {

public void aspectTest(String a,String b){
System.out.println("in aspectTest:" + "a:" + a + ",b:" + b);
}
}
@Pointcut(value="execution(* bean.HelloApi.aspectTest(..)) && args(a1,b2)",argNames="a1,b2")
public void pointcut1(String a1,String b2){}

@Before(value="pointcut1(a,b)",argNames="a,b")
public void beforecase1(String a,String b){
System.out.println("1 a:" + a +" b:" + b);
}

// NOTE: a and p locations
@Before(value="pointcut1(a,b)",argNames="b,a")
public void beforecase2(String a,String b){
System.out.println("2 a:" + a +" b:" + b);
}
HelloApi helloapi1 = beanFactory.getBean("helloapi1",HelloApi.class);
helloapi1.aspectTest("a", "b");

Output

1 a:a b:b
2 a:b b:a
in aspectTest:a:a,b:b

API Design Guide

· 2 min read

Open in Notion

Use RESTful service URLs

Under REST principles, a URL identifies a resource. The following URL design patterns are considered REST best practices:

  • URLs should include nouns, not verbs.
  • Use plural nouns only for consistency (no singular nouns).
  • Use HTTP methods (HTTP/1.1) to operate on these resources:
  • Use HTTP response status codes to represent the outcome of operations on resources.

Response Http Status Code

  • 200 OK
  • 400 Bad Request
  • 500 Internal Server Error

Other commonly seen codes include:

  • 201 Created
  • 204 No Content
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found

jQuery Ajax Request

  • type: ‘GET’ —- dataType: ‘xml|json|script|text|html’ -> response http status code: 200 / 404
  • type: ‘POST’ —– dataType: ‘xml|json’ -> response http status code: 201 (created) / 405 (Method Not Allowed)
  • type: ‘PUT’ —— dataType: ‘xml|json’ -> response http status code: 200 / 404
  • type: ‘DELETE’ —— if return 204, dataType should not be ‘json’ -> response http status code: 200 / 404

Good RESTful URL examples

List of magazines:

GET /api/v1/magazines.json HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

Filtering and sorting are server-side operations on resources:

GET /api/v1/magazines.json?year=2011&amp;sort=desc HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

A single magazine in JSON format:

GET /api/v1/magazines/1234.json HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

All articles in (or belonging to) this magazine:

GET /api/v1/magazines/1234/articles.json HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

All articles in this magazine in XML format:

GET /api/v1/magazines/1234/articles.xml HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

Specify query parameters in a comma separated list:

GET /api/v1/magazines/1234.json?fields=title,subtitle,date HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

Add a new article to a particular magazine:

POST /api/v1/magazines/1234/articles.json HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

Build Spring Boot Starter Project

· One min read

Open in Notion

Two Java Files

@ConfigurationProperties(prefix = "spring.ftsi")
public class IndexServiceAutoConfigurationProperties {

}

@Configuration
@EnableConfigurationProperties(IndexServiceAutoConfigurationProperties.class)
@ConditionalOnClass(IndexService.class)
@ConditionalOnProperty(prefix = "spring.ftsi", name = "enabled", matchIfMissing = true)
public class IndexServiceAutoConfiguration {

@Autowired
private IndexServiceAutoConfigurationProperties properties;

@Bean
@ConditionalOnMissingBean(IndexService.class)
public IndexService indexService() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
IndexService service = new IndexService();
return service;
}
}

File: /src/main/resources/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
net.bndy.ftsi.starter.IndexServiceAutoConfiguration

Usage

application.yml

spring:
ftsi:
property1: ...
property2: ...

java

@SpringBootApplication
public class Application {

@Autowried
IndexService indexService;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Download File via AJAX

· One min read

Open in Notion

$('#GetFile').on('click', function () {
$.ajax({
url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
method: 'GET',
xhrFields: {
responseType: 'blob'
},
success: function (response) {
var blob = response.data;
var a = document.createElement('a');
var fileUrl = window.URL.createObjectURL(blob);
var filename = 'myfile.pdf';
/* you can also use below to get filename from backend */
// var contentDisposition = response.headers('Content-Disposition');
// var matches = /filename=\\"(.+?)\\"/g.exec(contentDisposition);
// var filename = matches && matches.length > 1 ? matches\[1\] : '';
/* but need add backend code like below, otherwise you cannot get Content-Disposition header */
// + response.setHeader("Content-Disposition", "attachment; filename=\\"myfile.csv\\"");
// + response.setHeader("Content-type", "application/octet-stream;charset=utf-8");
// + response.setHeader("Access-Control-Expose-Headers","Content-Disposition");
// work in IE
if (window.navigator && window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blob, filename);
return;
}
a.href = fileUrl;
a.download = filename;
a.dispatchEvent(new MouseEvent('click'));
setTimeout(function() {
a.remove();
window.URL.revokeObjectURL.bind(window.URL, fileUrl);
});
}
});
});

Enable https in Spring Boot

· One min read

Open in Notion

  1. Create Self Signed SSL Certificate

    keytool -genkeypair -alias filenameAlias -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore filename.p12 -validity 3650

    And use below code to view certificate details.

    keytool -list -keystore filename.p12
  2. Enabling HTTPS in Spring Boot application.propreties

    # The format used for the keystore. for JKS, set it as JKS
    server.ssl.key-store-type=PKCS12
    # The path to the keystore containing the certificate
    server.ssl.key-store=classpath:keystore/javadevjournal.p12
    # The password used to generate the certificate
    server.ssl.key-store-password=use the same password which we added during certificate creation
    # The alias mapped to the certificate
    server.ssl.key-alias=javadevjournal
    # Run Spring Boot on HTTPS only, default 443 like no https 80 port
    server.port=8443

Note that you need copy filename.p12 to src/main/resources/keystore folder

Error Handling in Spring Boot

· 3 min read

Open in Notion

Disabling the Whitelabel Error Page

S1: In application.properties server.error.whitelabel.enabled=false

S2: Excluding the ErrorMvcAutoConfiguration bean

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
#for Spring Boot 2.0
#spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration

Or by adding this annotation to the main class:

@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})

Custom ErrorController

@Controller
public class MyErrorController implements ErrorController {
@RequestMapping("/error")
public String handleError() {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
// get the errors
Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());

if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "error-404";
} else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "error-500";
}
}
return "error";
}

@Override
public String getErrorPath() {
return "/error";
}
}

在 Spring 中常见的全局异常处理,主要有三种:

(1)注解 ExceptionHandler

(2)继承 HandlerExceptionResolver 接口

(3)注解 ControllerAdvice

@Controller
@RequestMapping("c")
public class Controller1 {
// **/c/query -> response 400
@ResponseBody
@ResponseMapping(value = "/query", produces = "appliation/json;charset=UTF-8")
public String query(@RequestParam("id") Long id) {
return id;
}

// **/c/calc -> response 500
@ResponseBody
@ResponseMapping(value = "/calc", produces = "application/json;charset=UTF-8")
public String calc() {
int a = 2/0;
return "":
}
}

注解 ExceptionHandler

注解 ExceptionHandler 作用对象为方法,最简单的使用方法就是放在 controller 文件中,详细的注解定义不再介绍。如果项目中有多个 controller 文件,通常可以在 baseController 中实现 ExceptionHandler 的异常处理,而各个 contoller 继承 basecontroller 从而达到统一异常处理的目的。因为比较常见,简单代码如下:

@ExceptionHandler(Exception.class)
@ResponseBody
public String exception(Exception ex) {
return this.getClass().getSimpleName() + ": " + ex.getMessage();
}

优点:ExceptionHandler 简单易懂,并且对于异常处理没有限定方法格式; 缺点:由于 ExceptionHandler 仅作用于方法,对于多个 controller 的情况,仅为了一个方法,所有需要异常处理的 controller 都继承这个类,明明不相关的东西,强行给他们找个爹,不太好。

注解 ControllerAdvice

这里虽说是 ControllerAdvice 注解,其实是其与 ExceptionHandler 的组合使用。在上文中可以看到,单独使用 @ExceptionHandler 时,其必须在一个 Controller 中,然而当其与 ControllerAdvice 组合使用时就完全没有了这个限制。换句话说,二者的组合达到的全局的异常捕获处理。 这种方法将所有的异常处理整合到一处,去除了 Controller 中的继承关系,并且达到了全局捕获的效果,推荐使用此类方式; Controller 中单独 @ExceptionHandle 异常处理排在了首位,@ControllerAdvice 排在了第二位。

@ControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public String exceptionHandler(Exception ex) {
return "";
}
}

实现 HandlerExceptionResolver 接口

HandlerExceptionResolver 本身 SpringMVC 内部的接口,其内部只有 resolveException 一个方法,通过实现该接口我们可以达到全局异常处理的目的。

@Component
public class MyHandlerExcptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
PrintWriter writer = response.getWriter();
writer.write(ex.getMessage());
writer.flush();
writer.close();

return new ModelAndView();
}
}

可以看到 500 的异常处理已经生效了,但是 400 的异常处理却没有生效,并且根没有异常前的返回结果一样。这是怎么回事呢?不是说可以做到全局异常处理的么?

经过DefaultHandlerExceptionResolver异常处理源码分析,可以看到我们的自定义类 MyHandlerExceptionResolver 确实可以做到全局处理异常,只不过对于 query 请求的异常,中间被 DefaultHandlerExceptionResolver 插了一脚,所以就跳过了 MyHandlerExceptionResolver 类的处理,从而出现 400 的返回结果。