From 9d8b2cec0a2fe395617e9d3f7af7ae915fc2c7a7 Mon Sep 17 00:00:00 2001 From: Mario Mol Date: Mon, 13 Apr 2026 01:16:53 +0200 Subject: [PATCH 1/9] fix: correct RG invalid example and improve BrazilValidateComponent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix DATAERROR.rg: 'MG1012345' was passing validation because any value prefixed with a valid Brazilian state code (MG is in CEPRange) is unconditionally accepted by validate_rg. Replaced with 'ZZ-10.123.456' (invalid state code) so the invalid column correctly shows ✗ invalid. Refs mariohmol/react-brazil#1 - Refactor BrazilValidateComponent to accept value/format/state props and use validateBr from js-brasil - Rewrite example App with live mask+validate table and static valid/invalid table - Fix titulo example value (valid check digit) - Expand test suite coverage Co-Authored-By: Claude Sonnet 4.6 --- example/package.json | 15 ++++- example/src/App.js | 156 +++++++++++++++++++++++++++++++++++-------- example/src/utils.js | 4 +- package.json | 12 ++-- src/validate.tsx | 39 ++++++----- tests/test.tsx | 50 +++++++++++++- 6 files changed, 218 insertions(+), 58 deletions(-) diff --git a/example/package.json b/example/package.json index 9604867..8a0fea9 100644 --- a/example/package.json +++ b/example/package.json @@ -1,10 +1,11 @@ { "name": "react-brazil-example", - "homepage": "https://mariohmol.github.io/react-brazil", + "homepage": "/react-brazil", "version": "0.0.0", "license": "MIT", "private": true, "dependencies": { + "ajv": "^8.18.0", "prop-types": "^15.6.2", "react": "^16.4.1", "react-brazil": "file:..", @@ -17,5 +18,17 @@ "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] } } diff --git a/example/src/App.js b/example/src/App.js index aa52c81..9771523 100644 --- a/example/src/App.js +++ b/example/src/App.js @@ -1,46 +1,144 @@ import React, { Component } from 'react' import { - BrazilFormatComponent, BrazilMaskComponent, BrazilValidateComponent } from 'react-brazil' -import { - // estados, - // DATAERROR, - // DATARAW, - DATA, -} from './utils'; +import { DATA, DATAERROR } from './utils'; -export default class App extends Component { +const FORMATS = ['cpf', 'cnpj', 'cep', 'rg', 'telefone', 'placa', 'renavam', 'titulo', 'time', 'currency'] + +const css = { + page: { maxWidth: 860, margin: '0 auto', padding: '24px 16px', fontFamily: 'sans-serif' }, + table: { width: '100%', borderCollapse: 'collapse', marginBottom: 32 }, + th: { textAlign: 'left', padding: '8px 12px', background: '#f6f8fa', borderBottom: '2px solid #ddd', fontWeight: 600 }, + td: { padding: '8px 12px', borderBottom: '1px solid #eee', verticalAlign: 'middle' }, + badge: (ok, empty) => ({ + display: 'inline-block', + padding: '2px 10px', + borderRadius: 12, + fontWeight: 600, + fontSize: 13, + background: empty ? '#f6f8fa' : ok ? '#dafbe1' : '#ffe3e3', + color: empty ? '#888' : ok ? '#1a7f37' : '#cf222e', + minWidth: 80, + textAlign: 'center', + }), + mono: { fontFamily: 'monospace', fontSize: 13 }, +} + +const inputStyle = { + padding: '6px 10px', + fontFamily: 'monospace', + border: '1px solid #ccc', + borderRadius: 4, + width: 200, + fontSize: 14, +} + +class ValidatedRow extends Component { + constructor(props) { + super(props) + this.state = { value: DATA[props.format] || '' } + } + + render() { + const { format } = this.props + const { value } = this.state + // treat as empty when only mask placeholders remain (underscores) + const empty = !value || value.replace(/[_]/g, '').replace(/[^a-zA-Z0-9]/g, '').trim() === '' + return ( + + {format} + + this.setState({ value: e.target.value })} + /> + + + {empty ? ( + — + ) : ( + + {isValid => ( + + {isValid ? '✓ valid' : '✗ invalid'} + + )} + + )} + + + ) + } +} +export default class App extends Component { render() { return ( -
+

React Brazil

-

Format

- {Object.keys(DATA).map(key=>(
-

{key}:

- -
))} - -

Mask

- {Object.keys(DATA).map(key=>(
-

{key}:

- { typeof DATA[key] === 'string' ? - : ''} -
))} - -

Validate

- {Object.keys(DATA).map(key=>(
-

{key}:

- -
))} + {/* ── MASK + LIVE VALIDATE ── */} +

Mask & Validate

+

+ Inputs use BrazilMaskComponent pre-filled with example values. + Edit any field to see BrazilValidateComponent update in real time. +

+ + + + + + + + + + {FORMATS.map(f => )} + +
FormatMasked inputValid?
+ + {/* ── STATIC VALID/INVALID ── */} +

Static examples

+

+ Known-valid vs known-invalid values passed directly to BrazilValidateComponent. +

+ + + + + + + + + + + + {FORMATS.map(key => DATA[key] && DATAERROR[key] && typeof DATA[key] === 'string' ? ( + + + + + + + + ) : null)} + +
FormatValid valueInvalid value
{key}{DATA[key]} + + {isValid => {isValid ? '✓ valid' : '✗ invalid'}} + + {DATAERROR[key]} + + {isValid => {isValid ? '✓ valid' : '✗ invalid'}} + +
) } - -} \ No newline at end of file +} diff --git a/example/src/utils.js b/example/src/utils.js index 5b496e2..73cb706 100644 --- a/example/src/utils.js +++ b/example/src/utils.js @@ -38,7 +38,7 @@ export const DATA = { number: '1.234,56', time: '06:33', placa: 'ADJ-5468', - titulo: '2053.9588.0384' + titulo: '2053.9588.0310' } // const rawIE = {}; @@ -98,7 +98,7 @@ export const DATAERROR = { cpf: '15663188151', cnpj: '40841253000101', cep: '3123456', - rg: 'MG1012345', + rg: 'ZZ-10.123.456', telefone: '319999999', inscricaoestadual: { ac: '01004823001126', diff --git a/package.json b/package.json index 02cc6d0..01bec7b 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "prepare": "npm run build", "predeploy": "cd example && npm install && npm run build", "deploy": "gh-pages -d example/build", + "deploy:demo": "node node_modules/.bin/rollup -c && cd example && npm run build && cp -r build/. /var/www/test_counbo/react-brazil/", "publish-npm": "npm run build && npm publish" }, "dependencies": { @@ -30,7 +31,6 @@ "@types/enzyme-adapter-react-16": "^1.0.5", "@types/react-text-mask": "^5.4.6", "js-brasil": "^1.4.3", - "node-sass": "^9.0.0", "react-text-mask": "^5.4.3" }, "peerDependencies": { @@ -53,12 +53,14 @@ "react": "^16.4.1", "react-dom": "^16.4.1", "react-scripts-ts": "^4.0.8", - "rollup": "^0.62.0", + "rollup": "^1.32.1", "rollup-plugin-babel": "^3.0.7", - "rollup-plugin-commonjs": "^9.1.3", - "rollup-plugin-node-resolve": "^3.3.0", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-peer-deps-external": "^2.2.0", - "rollup-plugin-postcss": "^2.0.3", + "fastparse": "^1.1.2", + "rollup-plugin-postcss": "^1.6.3", + "sass": "^1.77.0", "rollup-plugin-typescript2": "^0.25.2", "rollup-plugin-url": "^1.4.0", "ts-jest": "^24.0.2", diff --git a/src/validate.tsx b/src/validate.tsx index 11374cb..15f1e7f 100644 --- a/src/validate.tsx +++ b/src/validate.tsx @@ -1,31 +1,30 @@ -/** - * @class JSGanttComponent - */ import * as React from 'react' -// import * as jsbrasil from 'js-brasil'; +import { validateBr } from 'js-brasil'; import './styles.scss'; export type Props = { - data: Array, - options: Object + value: any, + format: string, + state?: string, + children?: (isValid: boolean) => React.ReactNode } export default class BrazilValidateComponent extends React.Component { - - // componentDidMount() { - // this.makeChart(); - // } - // componentDidUpdate() { - // this.makeChart(); - // } + render() { + const { value, format, state, children } = this.props; + let isValid = false; + if (validateBr[format]) { + if (format === 'inscricaoestadual' && state) { + isValid = validateBr[format](value, state); + } else { + isValid = validateBr[format](value); + } + } - render() { - return ( - - Validate - - ) + if (children) { + return {children(isValid)}; + } + return {value}; } } - diff --git a/tests/test.tsx b/tests/test.tsx index fe22b48..cec613a 100644 --- a/tests/test.tsx +++ b/tests/test.tsx @@ -5,7 +5,7 @@ import BrazilValidateComponent from '../src/validate' import { shallow, mount } from 'enzyme'; -import { DATA, DATARAW } from './utils'; +import { DATA, DATARAW, DATAERROR } from './utils'; import React from 'react'; describe('BrazilFormatComponent', () => { @@ -52,4 +52,52 @@ describe('BrazilValidateComponent', () => { it('is truthy', () => { expect(BrazilValidateComponent).toBeTruthy() }); + + const validFormats = ['cpf', 'cnpj', 'cep', 'rg', 'placa', 'titulo']; + + it('returns true for valid values', () => { + for (const key of validFormats) { + const wrapper = shallow( + + ); + expect(wrapper.find('span').prop('data-valid')).toBe(true); + } + }); + + it('returns false for invalid values', () => { + const errorFormats = ['cpf', 'cnpj', 'cep', 'rg', 'placa']; + for (const key of errorFormats) { + const wrapper = shallow( + + ); + expect(wrapper.find('span').prop('data-valid')).toBe(false); + } + }); + + it('calls children render prop with isValid boolean', () => { + let received: boolean | undefined; + shallow( + + {(isValid: boolean) => { received = isValid; return null; }} + + ); + expect(received).toBe(true); + }); + + it('calls children render prop with false for invalid value', () => { + let received: boolean | undefined; + shallow( + + {(isValid: boolean) => { received = isValid; return null; }} + + ); + expect(received).toBe(false); + }); + + it('returns false for unknown format', () => { + const wrapper = shallow( + + ); + expect(wrapper.find('span').prop('data-valid')).toBe(false); + }); }); From 2e10608caeae9339da7197015071cb644c58dace Mon Sep 17 00:00:00 2001 From: Mario Mol Date: Mon, 13 Apr 2026 01:25:00 +0200 Subject: [PATCH 2/9] feat: add docs/ folder for GitHub Pages and update deploy scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build the example app into docs/ so GitHub Pages can serve it from the master branch docs/ folder (Settings → Pages → /docs). - Set example/package.json homepage to https://mariohmol.github.io/react-brazil - Add deploy script that copies example/build/. into docs/ - Add deploy:gh-pages convenience script (build + copy) - Keep deploy:demo for the test.counbo.com server Co-Authored-By: Claude Sonnet 4.6 --- docs/asset-manifest.json | 13 +++++ docs/index.html | 1 + docs/manifest.json | 8 +++ docs/static/css/main.25f996dd.css | 2 + docs/static/css/main.25f996dd.css.map | 1 + docs/static/js/main.c8819b2c.js | 3 ++ docs/static/js/main.c8819b2c.js.LICENSE.txt | 56 +++++++++++++++++++++ docs/static/js/main.c8819b2c.js.map | 1 + example/package.json | 2 +- package.json | 5 +- 10 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 docs/asset-manifest.json create mode 100644 docs/index.html create mode 100644 docs/manifest.json create mode 100644 docs/static/css/main.25f996dd.css create mode 100644 docs/static/css/main.25f996dd.css.map create mode 100644 docs/static/js/main.c8819b2c.js create mode 100644 docs/static/js/main.c8819b2c.js.LICENSE.txt create mode 100644 docs/static/js/main.c8819b2c.js.map diff --git a/docs/asset-manifest.json b/docs/asset-manifest.json new file mode 100644 index 0000000..650690b --- /dev/null +++ b/docs/asset-manifest.json @@ -0,0 +1,13 @@ +{ + "files": { + "main.css": "/react-brazil/static/css/main.25f996dd.css", + "main.js": "/react-brazil/static/js/main.c8819b2c.js", + "index.html": "/react-brazil/index.html", + "main.25f996dd.css.map": "/react-brazil/static/css/main.25f996dd.css.map", + "main.c8819b2c.js.map": "/react-brazil/static/js/main.c8819b2c.js.map" + }, + "entrypoints": [ + "static/css/main.25f996dd.css", + "static/js/main.c8819b2c.js" + ] +} \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..6df1ed3 --- /dev/null +++ b/docs/index.html @@ -0,0 +1 @@ +react-brazil
\ No newline at end of file diff --git a/docs/manifest.json b/docs/manifest.json new file mode 100644 index 0000000..ae4751d --- /dev/null +++ b/docs/manifest.json @@ -0,0 +1,8 @@ +{ + "short_name": "react-brazil", + "name": "react-brazil", + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/docs/static/css/main.25f996dd.css b/docs/static/css/main.25f996dd.css new file mode 100644 index 0000000..ece78ef --- /dev/null +++ b/docs/static/css/main.25f996dd.css @@ -0,0 +1,2 @@ +body{font-family:sans-serif;margin:0;padding:0}h1{padding-top:40px}.test{border:2px solid #000;display:inline-block;font-size:2em;margin:2em auto}.container{margin:0 auto;width:800px} +/*# sourceMappingURL=main.25f996dd.css.map*/ \ No newline at end of file diff --git a/docs/static/css/main.25f996dd.css.map b/docs/static/css/main.25f996dd.css.map new file mode 100644 index 0000000..fc1a9f6 --- /dev/null +++ b/docs/static/css/main.25f996dd.css.map @@ -0,0 +1 @@ +{"version":3,"file":"static/css/main.25f996dd.css","mappings":"AAAA,KAGI,sBAAuB,CAFvB,QAAS,CACT,SAEJ,CAEA,GACI,gBACJ,CAEA,MAGI,qBAAsB,CAFtB,oBAAqB,CAGrB,aAAc,CAFd,eAGJ,CAEA,WAEI,aAAc,CADd,WAEJ","sources":["index.css"],"sourcesContent":["body {\n margin: 0;\n padding: 0;\n font-family: sans-serif;\n}\n\nh1 {\n padding-top: 40px;\n}\n\n.test {\n display: inline-block;\n margin: 2em auto;\n border: 2px solid #000;\n font-size: 2em;\n}\n\n.container {\n width: 800px;\n margin: 0 auto;\n}"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/docs/static/js/main.c8819b2c.js b/docs/static/js/main.c8819b2c.js new file mode 100644 index 0000000..639e6a4 --- /dev/null +++ b/docs/static/js/main.c8819b2c.js @@ -0,0 +1,3 @@ +/*! For license information please see main.c8819b2c.js.LICENSE.txt */ +(()=>{"use strict";var e={865(e){var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map(function(e){return t[e]}).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach(function(e){r[e]=e}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(a){return!1}}()?Object.assign:function(e,a){for(var i,o,l=function(e){if(null===e||void 0===e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),u=1;uA.length&&A.push(e)}function R(e,t,n,r){var a=typeof e;"undefined"!==a&&"boolean"!==a||(e=null);var l=!1;if(null===e)l=!0;else switch(a){case"string":case"number":l=!0;break;case"object":switch(e.$$typeof){case i:case o:l=!0}}if(l)return n(r,e,""===t?"."+j(e,0):t),1;if(l=0,t=""===t?".":t+":",Array.isArray(e))for(var u=0;u