• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

akvo / nmis-mobile / 6092208865

06 Sep 2023 03:01AM UTC coverage: 87.787% (+0.3%) from 87.486%
6092208865

push

github

web-flow
Merge pull request #147 from akvo/feature/146-autofield-poc

Feature/146 autofield poc

765 of 947 branches covered (0.0%)

Branch coverage included in aggregate %.

79 of 79 new or added lines in 2 files covered. (100.0%)

1643 of 1796 relevant lines covered (91.48%)

20.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.83
/app/src/form/fields/TypeAutofield.js
1
import React, { useEffect, useState } from 'react';
2
import { View } from 'react-native';
3
import { FieldLabel } from '../support';
4
import { styles } from '../styles';
5
import { Input } from '@rneui/themed';
6

7
const fnRegex = /^function(?:.+)?(?:\s+)?\((.+)?\)(?:\s+|\n+)?\{(?:\s+|\n+)?((?:.|\n)+)\}$/m;
4✔
8
const fnEcmaRegex = /^\((.+)?\)(?:\s+|\n+)?=>(?:\s+|\n+)?((?:.|\n)+)$/m;
4✔
9
const sanitize = [
4✔
10
  {
11
    prefix: /return fetch|fetch/g,
12
    re: /return fetch(\(.+)\} +|fetch(\(.+)\} +/,
13
    log: 'Fetch is not allowed.',
14
  },
15
];
16

17
const checkDirty = (fnString) => {
4✔
18
  return sanitize.reduce((prev, sn) => {
16✔
19
    const dirty = prev.match(sn.re);
16✔
20
    if (dirty) {
16!
21
      return prev.replace(sn.prefix, '').replace(dirty[1], `console.error("${sn.log}");`);
×
22
    }
23
    return prev;
16✔
24
  }, fnString);
25
};
26

27
const getFnMetadata = (fnString) => {
4✔
28
  const fnMetadata = fnRegex.exec(fnString) || fnEcmaRegex.exec(fnString);
16✔
29
  if (fnMetadata?.length >= 3) {
16✔
30
    const fn = fnMetadata[2].split(' ');
13✔
31
    return fn[0] === 'return' ? fnMetadata[2] : `return ${fnMetadata[2]}`;
13✔
32
  }
33
  return false;
3✔
34
};
35

36
const generateFnBody = (fnMetadata, values) => {
4✔
37
  if (!fnMetadata) {
16✔
38
    return false;
3✔
39
  }
40

41
  const fnMetadataTemp = fnMetadata
13✔
42
    .trim()
43
    .split(' ')
44
    .map((f) => (f = f.trim()));
83✔
45

46
  // save defined condition to detect how many condition on fn
47
  // or save the total of condition inside fn string
48
  const fnBodyTemp = [];
13✔
49

50
  // generate the fnBody
51
  const fnBody = fnMetadataTemp.map((f) => {
13✔
52
    f = f.trim();
83✔
53
    const meta = f.match(/#([0-9]*)/);
83✔
54
    if (meta) {
83✔
55
      fnBodyTemp.push(f); // save condition
29✔
56
      let val = values?.[meta[1]];
29✔
57
      if (!val) {
29✔
58
        return null;
3✔
59
      }
60
      if (typeof val === 'object') {
26✔
61
        if (Array.isArray(val)) {
10✔
62
          val = val.join(',');
6✔
63
        } else {
64
          if (val?.lat) {
4✔
65
            val = `${val.lat},${val.lng}`;
3✔
66
          } else {
67
            val = null;
1✔
68
          }
69
        }
70
      }
71
      if (typeof val === 'number') {
26✔
72
        val = Number(val);
16✔
73
      }
74
      if (typeof val === 'string') {
26✔
75
        val = `"${val}"`;
9✔
76
      }
77
      const fnMatch = f.match(/#([0-9]*|[0-9]*\..+)+/);
26✔
78
      if (fnMatch) {
26!
79
        val = fnMatch[1] === meta[1] ? val : val + fnMatch[1];
26✔
80
      }
81
      return val;
26✔
82
    }
83
    const n = f.match(/(^[0-9]*$)/);
54✔
84
    if (n) {
54✔
85
      return Number(n[1]);
6✔
86
    }
87
    return f;
48✔
88
  });
89

90
  // all fn conditions meet, return generated fnBody
91
  if (!fnBody.filter((x) => !x).length) {
83✔
92
    return fnBody.join(' ');
9✔
93
  }
94

95
  // return false if generated fnBody contains null align with fnBodyTemp
96
  // or meet the total of condition inside fn string
97
  if (fnBody.filter((x) => !x).length === fnBodyTemp.length) {
11✔
98
    return false;
3✔
99
  }
100

101
  // remap fnBody if only one fnBody meet the requirements
102
  return fnBody
1✔
103
    .map((x, xi) => {
104
      if (!x) {
5✔
105
        const f = fnMetadataTemp[xi];
1✔
106
        const splitF = f.split('.');
1✔
107
        if (splitF.length) {
1!
108
          splitF[0] = `"${splitF[0]}"`;
1✔
109
        }
110
        return splitF.join('.');
1✔
111
      }
112
      return x;
4✔
113
    })
114
    .join(' ');
115
};
116

117
const strToFunction = (fnString, values) => {
4✔
118
  fnString = checkDirty(fnString);
16✔
119
  const fnMetadata = getFnMetadata(fnString);
16✔
120
  const fnBody = generateFnBody(fnMetadata, values);
16✔
121
  try {
16✔
122
    return new Function(fnBody);
16✔
123
  } catch (error) {
124
    return false;
1✔
125
  }
126
};
127

128
const TypeAutofield = ({ onChange, values, keyform, id, name, tooltip, fn }) => {
4✔
129
  const [value, setValue] = useState(null);
16✔
130
  const { fnString } = fn;
16✔
131
  const automateValue = strToFunction(fnString, values);
16✔
132

133
  useEffect(() => {
16✔
134
    if (automateValue) {
11✔
135
      setValue(automateValue());
10✔
136
    } else {
137
      setValue(null);
1✔
138
    }
139
  }, [automateValue, fnString]);
140

141
  useEffect(() => {
16✔
142
    if (onChange) {
11!
143
      onChange(id, value);
11✔
144
    }
145
  }, [value]);
146

147
  return (
16✔
148
    <View>
149
      <FieldLabel keyform={keyform} name={name} tooltip={tooltip} />
150
      <Input
151
        inputContainerStyle={styles.autoFieldContainer}
152
        value={value}
153
        testID="type-autofield"
154
        disabled
155
      />
156
    </View>
157
  );
158
};
159

160
export default TypeAutofield;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc