I have created a function that generates an array of anonymous functions based on string of rules given as the parameter. However, my test are failing:
$ jest -t "unit/validationRules generates correct validation rules"
FAIL test/unit/composables/validationRules.spec.js
● unit/validationRules › generates correct validation rules
expect(received).toEqual(expected) // deep equality
Expected: [[Function anonymous], [Function anonymous], [Function anonymous]]
Received: serializes to the same string
11 | ]
12 |
> 13 | expect(rules).toEqual(expected)
| ^
14 | })
15 | })
16 |
at Object.<anonymous> (test/unit/composables/validationRules.spec.js:13:19)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 0 | 0 | 0 | 0 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 failed, 2 skipped, 1 of 3 total
Tests: 1 failed, 5 skipped, 6 total
Snapshots: 0 total
Time: 7.779 s
Ran all test suites with tests matching "unit/validationRules generates correct validation rules".
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
My implementation look like this
tests/unit/composables/validationRules.spec.js
import { useRules } from '~/composables'
describe('unit/validationRules', () => {
it('generates correct validation rules', () => {
const rules = useRules('required|string|max:255')
const expected = [
(x) => !!x || 'This field is required',
(x) => typeof x === 'string' || 'The given value must be a string',
(x) => (!!x || '').length <= 255 || 'A maximum of 255 characters allowed',
]
expect(rules).toEqual(expected)
})
})
composables/validationRules.js
/**
* Creates validation rules based on the given string of rules.
*
* You can set multiple rules by chaining each using `|` (pipe)
* character, including conditional rule using `:`.
*
* Sample usage: `required|string|max:255`
*
* #param {String} rules Rules to be used for input validation.
* #returns {Array<Function<String|Boolean>>}
*/
export const useRules = (rules) => {
const splitRules = rules.toLowerCase().split('|')
const generatedRules = []
for (let i = 0; i < splitRules.length; i++) {
const rule = splitRules[i]
if (rule.includes(':')) {
const conditions = rule.split(':')
if (conditions.length < 2) {
continue
}
const key = conditions[0]
const val = conditions[1]
if (key === 'max') {
generatedRules.push(
(x) =>
(!!x || '').length <= val ||
`A maximum of ${val} characters allowed`
)
}
} else {
if (rule === 'required') {
generatedRules.push((x) => !!x || 'This field is required')
}
if (rule === 'string') {
generatedRules.push(
(x) => typeof x === 'string' || 'The given value must be a string'
)
}
}
}
return generatedRules
}
There is similar question to this but I think my case is different since I don't want to get the return value of the anonymous functions and wanted to compare the function instead.
Can you guys help me?
Depending on what you want to test with "function equality", there are multiple scenarios. The only reasonable ways in my opinion are (1) and (2), the rest is for the curious reader.
Test some specific inputs
For instance, for (x) => !!x || 'This field is required', you could test expect(rules[0](undefined)).toEqual('This field is required) and expect(rules[0](123).toEqual(true). This does not cover all cases, but should cover the most important ones.
Don't create the function inline
If you use functions like createMaxRule(max) instead of an inline function, you could mock them with jest to return the expected function and afterwards check it for equality. In this case, rules[0] === expected[0] returns true, because they are the same function (not only equivalent).
Compare the function definition
If you know that the test function and the implementation are defined in the same way, you could convert them to strings and compare them, e.g. rules[0].toString() === expected[0].toString(). This will break as soon as a function is implemented differently, even if they behave the same way
Prove the equality
I don't know if this is possible in JavaScript and I would never use this in a unit test. But you could also automatically reason about all possible inputs and use a prover that verifies the equivalence of the functions
Go through all possible inputs
If the input set is limited (e.g. for a boolean with only true, false) you could go through all possible inputs and check if the output is the same. However, this is impossible for e.g. strings, as there are infinite string values
Related
I have an interface with
interface mathTest {
mathAction: MathActionEnum;
}
The reason for this is that I want this property to have just one of the specific values from the enum below.
enum MathActionEnum {
'byOne' = 1,
'byTwo' = 2,
'byFour' = 4,
'byEight' = 8,
}
Assume mathAction = 'byOne' -> received from an API response.
First scenario: doing an arithmetic operation, I need the number value: let result: number = amount / MathActionEnum[mathAction] but I get an error:
The right-hand side of an arithmetic operation must be of type 'any',
'number', 'bigint' or an enum type
It is a number but still I need to cast it with Number(MathActionEnum[mathAction]) for the error to go away.
Second scenario: equality check, I need the string value: if (mathAction === MathActionEnum[MathActionEnum.byOne]) but I get an error:
This condition will always return 'false' since the types
'MathActionEnum' and 'string' have no overlap
Which makes sense.
I'm a bit lost, is there a way to syntax it as I expect it to be? Maybe I need to define things differently?
Thanks
TypeScript enums are absolutely NOT suitable for any sort of key-value mapping. The intent is to have a grouping of uniquely identifiable labels, but labels are where it ends. While they may indeed have a number representation under the hood, they are not intended for use as a key-value store. You will have to cast it to "extract the number", and then the type is just number, so you effectively defeat the purpose of enums.
For all intents and purposes, think of them like keys with no useful values:
const MathActionEnum = Object.freeze({
byOne: Symbol(),
byTwo: Symbol(),
byFour: Symbol(),
byEight: Symbol(),
})
Consider the newer alternative, const assertion, instead. They'll provide you with type safety on both keys and values:
const MathActions = {
'byOne': 1,
'byTwo': 2,
'byFour': 4,
'byEight': 8,
} as const
type MathAction = keyof typeof MathActions
type MathActionValue = typeof MathActions[MathAction]
You get full type safety on both keys and values:
const example = (action: MathAction) => {
return 2 * MathActions[action]
}
example('byOne')
// compile error, not a valid key
example('foo')
// -------------
const example2 = (actionValue: MathActionValue) => {
return 2 * actionValue
}
example2(4)
// compile error, not a valid value
example2(19)
You can even add type assertions to check if arbitrary values are a key or value:
const isAction = (action: string): action is MathAction => {
return Object.keys(MathActions).includes(action)
}
isAction
const isActionValue = (actionValue: number): actionValue is MathActionValue => {
return Object.values(MathActions).includes(actionValue as any)
}
You'll even get IDE autocompletion for both keys and values:
Here's a Playground
I am trying to make an excel export using Maatwebsite Excel. This is my second export file and first one is working perfect. When I var_dump() data is okay using postman but the rendered excel has all cells populated with 0s.
class SummaryExport implements FromArray, WithMapping, WithEvents, WithHeadings, WithStyles, WithCustomStartCell, WithStrictNullComparison, ShouldAutoSize
{
use RegistersEventListeners;
private $from_date, $to_date, $fee_type;
public function __construct($from_date, $to_date, $fee_type)
{
$this->from_date = $from_date;
$this->to_date = $to_date;
$this->fee_type = $fee_type;
}
/**
* #return array
*/
public function array(): array
{
$school_id = auth()->user()->school_id;
if($this->fee_type == "all"){
$fee_vouchers = FeeVoucher::where('school_id', $school_id)->where('issue_date','>=',$this->from_date)
->where('issue_date','<=',$this->to_date)->get();
}else{
$fee_vouchers = FeeVoucher::where('school_id', $school_id)->where('issue_date','>=',$this->from_date)
->where('issue_date','<=',$this->to_date)
->where('type', $this->fee_type)->get();
}
$gross = 0;
$concession = 0;
$fines=0;
$net=0;
$received=0;
$percentage=0;
$balance = 0;
$fee_summary = array();
foreach($fee_vouchers as $vchr){
$gross = $gross + $vchr->total_amount + $vchr->fines->sum('amount');
$concession = $concession + $vchr->concessions->sum('applied');
$fines = $fines + $vchr->fines->sum('amount');
if($vchr->is_paid == 1){
$received = $received + $vchr->total_amount + $vchr->fines->sum('amount');
}
}
$net = $gross - $concession;
$balance = $net - $received;
$fee_summary[]= array(
'gross' => intval($gross),
'concession' =>intval($concession),
'fines' => intval($fines),
'net' => intval($net),
'received' => intval($received),
'percentage' => intval($percentage),
'balance' => intval($balance)
);
//var_dump($fee_summary); //values are displayed correct here as well
return $fee_summary;
}
public function map($row): array
{
// When I do var_dump($row) here in post man I can see data but in Excel data is not written but all zeros
return [
$row['gross'],
$row['concession'],
$row['fines'],
$row['net'],
$row['received'],
$row['percentage'],
$row['balance']
//[15023,456,785,4567,865,952,0] // when I give static values the problem is fixed and all values are written in Excel
];
}
}
The weird thing is when I give the fixed static values it gives me proper results. On using var_dump it also shows that data is accurately created. But I am stuck from last night what is causing this problem.
I know this is quite old now, but the intval method will return zero on an unprocessable/incorrect value. As mentioned in the docs here, The integer value of value on success, or 0 on failure. Empty arrays return 0, non-empty arrays return 1.
echo intval(true) // 1
echo intval(false) // 0
echo intval("'12'") // 0, within the quotes the value is a string still
echo intval("12") // 12
echo intval(5.25) // 5
echo intval('$400') // 0
Without seeing the input data I assume that the values being passed into the intval method on return, aren't valid types. Are there symbols (i.e. $, other currency symbols) within the values?
Given the following code:
type CustomTypeA = {
x: number,
y: number,
}
type CustomTypeB = CustomTypeA & {
z: number,
}
type CustomType = CustomTypeA | CustomTypeB
export const myFunction = (a: CustomType | number): number | void => {
if (typeof a === 'number') {
return a; // THIS WORKS WELL, it's considered number after the check, which is correct
} else if (typeof a === CustomType) {
const newX = a.x // ERR: Flow: Cannot get `a.x` because property `x` is missing in `Number`
const newZ = a.z // SAME ERR, Flow: Cannot get `a.z` because property `z` is missing in `Number`.
}
}
Also, the typeof a === CustomType check is highlighted as an error as well:
Flow: Cannot reference type `CustomType` from a value position.
This however doesn't happen for the typeof a === 'number' one.
It's like the check against the custom object type I created is not valid/recognized.
Can someone explain why and possibly how to escape this?
Thanks.
Flow custom types are not values, they do not exist, they vanish after transpilation, therefore you can not use them with a JS operator like typeof because it requires a value. So when you do typeof a === CustomType it will fail, because after compilation you will end with typeof a === , CustomType is just stripped out and you end with invalid JS.
This seems to be a flow limitation to be honest.
There is the %checks operator which allows you to build type guard functions.
One may think you can use this feature to build a type refinement for your custom types with a function that has the proper logic, but nothing on its documentation suggest that it can be used to refine custom types.
It also requires the body of the guard function to be very simple so flow can understand what do you mean. Some type guard function examples may look like this (from flow docs):
function truthy(a, b): boolean %checks {
return !!a && !!b;
}
function isString(y): %checks {
return typeof y === "string";
}
function isNumber(y): %checks {
return typeof y === "number";
}
However when you try a more complex check, for example checking that something is an object, but it is not an array or a date, flow fails to understand your intention and the predicate function will not work. Something like this:
function isObject(obj: mixed): boolean %checks {
return Object.prototype.toString.call(obj) === '[object Object]'
}
Will fail because flow doesn't understand that as a type refinement for object. For that particular case, there is a workaround suggested on a github issue that consist on declaring the function on the type level asserting that it checks for the object type:
declare function isObject(obj: mixed): boolean %checks(obj instanceof Object)
But you can not use that either for your particular case, because you can not do instanceof on a custom type, because it is not a class.
So your options are either go verbose and check all the expected properties are present on a type check, like this:
if (typeof a.x === 'number' && typeof a.y === 'number' && typeof a.z === 'number') {
const {x: ax, y: ay, z: az} = a
// now you can safely use the extracted variables
Note you need to extract the props from the object because, any time you call a function flow will invalidate your type refinement and the next line that access a.x will fail.
You can declare your point as a Class, and use the type system checking for instances of that class.
Or you build a validation function that returns either the correct type or null, so flow can understand the type has been refined:
function isCustomType (p: mixed): CustomType | null {
const validated = ((p:any):CustomType)
if (typeof validated.x === 'number' && typeof validated.y === 'number') return validated
return null
}
const validA = isCustomType(a)
if (validA) {
const {x: ax, y: ay} = validA
// do stuff
This has the disadvantage that you need to allocate extra variables just to satisfy the type system, but I think that is a minor problem.
Also, it will not allow flow to validate the isCustomType function for you, because we are doing type casts to basically cheat flow. But given the surface is small and the objective is very focused it should be ok to be able to keep it manually correct.
I am working on app needs an NZBN Code to register with stripe payments, when a user can enter any NZBN that cause Stripe issues,
i entered the https://api.business.govt.nz/services/v4/nzbn to check it but no obivious api call contain the real check of the NZBN code .
is there any way please ?
EDIT:
i am passing the NZBN number as tax_id to Stripe so Stripe check it with what ever api and tell me that the tax_id is not a valid nzbn , but when i put a valid nzbn it takes it correclty how to prevent entering a not valid NZBN before going to stripe
This is a really old question but it has no answer, so I thought I'd share;
NZBN (different from the NZ GST number) is a plain old GTIN 13. This is my TypeScript for checking for 1) a valid GTIN, and then 2) a GTIN13 specifically
/**
* Boolean function to test code against GTIN published checksum method(s)
*/
export function isGTIN(text: number | string): boolean {
try {
const r = /[\D]/g,
gtin = typeof text === "number" ? text.toString().replace(r, "") : text.replace(r, ""),
paddedValue = gtin.padStart(14, "0")
if (!gtin.match(/^(\d{8}|\d{12,14})$/)) return false
let result = 0
for (let i = 0; i < paddedValue.length - 1; i += 1) {
result += parseInt(paddedValue.charAt(i), 10) * (i % 2 === 0 ? 3 : 1)
}
return (10 - (result % 10)) % 10 === parseInt(paddedValue.charAt(13), 10)
} catch (err) {
console.error(err.message)
return false
}
}
/**
* Boolean function to test NZBN (GTIN13) against published checksum method
*/
export function isNZBN(text: number | string): boolean {
try {
const r = /[\D]/g,
gtin = typeof text === "number" ? text.toString().replace(r, "") : text.replace(r, "")
// NZBN is a GTIN 13
if (gtin.length !== 13) return false
else return isGTIN(gtin)
} catch (err) {
console.error(err.message)
return false
}
}
While you could rely on validation error instead of implementing this yourself, you can also optionally pre-validate the input. This is really up to you.
Validation follows the OECD TIN specifications, such as for NZ IRDs, which looks to be 8 or 9 numeric digits. If the NZBN has a different spec than this, you'd need to implement a validation check before submitting.
The test is linked to this question here which I raised (& was resolved) a few days ago. My current test is:
// Helpers
function getObjectStructure(runners) {
const backStake = runners.back.stake || expect.any(Number).toBeGreaterThan(0)
const layStake = runners.lay.stake || expect.any(Number).toBeGreaterThan(0)
return {
netProfits: {
back: expect.any(Number).toBeGreaterThan(0),
lay: expect.any(Number).toBeGreaterThan(0)
},
grossProfits: {
back: (runners.back.price - 1) * backStake,
lay: layStake
},
stakes: {
back: backStake,
lay: layStake
}
}
}
// Mock
const funcB = jest.fn(pairs => {
return pairs[0]
})
// Test
test('Should call `funcB` with correct object structure', () => {
const params = JSON.parse(fs.readFileSync(paramsPath, 'utf8'))
const { arb } = params
const result = funcA(75)
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure(arb.runners)
)
)
)
})
The object structure of arb.runners is this:
{
"back": {
"stake": 123,
"price": 1.23
},
"lay": {
"stake": 456,
"price": 4.56
}
}
There are many different tests around this function mainly dependent upon the argument that is passed into funcA. For this example, it's 75. There's a different length of array that is passed to funcB dependent upon this parameter. However, it's now also dependent on whether the runners (back and/or lay) have existing stake properties for them. I have a beforeAll in each test which manipulates the arb in the file where I hold the params. Hence, that's why the input for the runners is different every time. An outline of what I'm trying to achieve is:
Measure the array passed into funcB is of correct length
Measure the objects within the array are of the correct structure:
2.1 If there are stakes with the runners, that's fine & the test is straight forward
2.2 If not stakes are with the runners, I need to test that; netProfits, grossProfits, & stakes properties all have positive Numbers
2.2 is the one I'm struggling with. If I try with my attempt below, the test fails with the following error:
TypeError: expect.any(...).toBeGreaterThan is not a function
As with previous question, the problem is that expect.any(Number).toBeGreaterThan(0) is incorrect because expect.any(...) is not an assertion and doesn't have matcher methods. The result of expect.any(...) is just a special value that is recognized by Jest equality matchers. It cannot be used in an expression like (runners.back.price - 1) * backStake.
If the intention is to extend equality matcher with custom behaviour, this is the case for custom matcher. Since spy matchers use built-in equality matcher anyway, spy arguments need to be asserted explicitly with custom matcher.
Otherwise additional restrictions should be asserted manually. It should be:
function getObjectStructure() {
return {
netProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
grossProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
stakes: {
back: expect.any(Number),
lay: expect.any(Number)
}
}
}
and
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledTimes(1);
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure()
)
)
)
const funcBArg = funcB.mock.calls[0][0];
const nonPositiveNetProfitsBack = funcBArg
.map(({ netProfits: { back } }, i) => [i, back])
.filter(([, val] => !(val > 0))
.map(([i, val] => `${netProfits:back:${i}:${val}`);
expect(nonPositiveNetProfitsBack).toEqual([]);
const nonPositiveNetProfitsLay = ...
Where !(val > 0) is necessary to detect NaN. Without custom matcher failed assertion won't result in meaningful message but an index and nonPositiveNetProfitsBack temporary variable name can give enough feedback to spot the problem. An array can be additionally remapped to contain meaningful values like a string and occupy less space in errors.