├── src ├── models │ ├── plugin.ts │ └── index.ts ├── abstracts │ ├── index.ts │ └── plugin.ts ├── interface │ ├── prop_option.ts │ ├── error.ts │ ├── component_option.ts │ ├── lazy_component.ts │ ├── directive.ts │ ├── attr_item.ts │ ├── directive_binding.ts │ ├── taj_store.ts │ ├── render_context.ts │ ├── index.ts │ ├── element_option.ts │ └── error_type.ts ├── utils │ ├── is_array.ts │ ├── merge.ts │ ├── get_object_keys.ts │ ├── promise_resolve.ts │ ├── is_object.ts │ ├── replace_null_prop.ts │ ├── hashify_array.ts │ ├── get_object_length.ts │ ├── for_own.ts │ ├── get_attribute.ts │ ├── resolve_path.ts │ ├── for_each.ts │ ├── get_data_type.ts │ ├── emit_state_change.ts │ ├── lazy_component.ts │ ├── next_tick.ts │ ├── index.ts │ ├── create_component.ts │ ├── dom.ts │ ├── init_component.ts │ └── set_attribute.ts ├── enums │ ├── index.ts │ ├── life_cycle_event.ts │ ├── html_tag.ts │ └── error_type.ts ├── helpers │ ├── create_text_node.ts │ ├── create_coment_node.ts │ ├── index_of.ts │ ├── execute_events.ts │ ├── set_component_mount.ts │ ├── get_el_key.ts │ ├── emit_error.ts │ ├── add_rc.ts │ ├── subscribe_to_emit_destory.ts │ ├── emit_update.ts │ ├── index.ts │ ├── handle_in_place.ts │ ├── comp_clear_all.ts │ ├── for_each_event.ts │ ├── destroy_helper.ts │ ├── handle_expression.ts │ └── handle_directive.ts ├── ready_made │ ├── index.ts │ ├── fragment_comp.ts │ ├── event_directive.ts │ ├── ref_directive.ts │ └── model_directive.ts ├── decorators │ ├── index.ts │ ├── reactive.ts │ ├── wrap_method_decorator.ts │ ├── computed.ts │ ├── children.ts │ ├── directive.ts │ ├── formatter.ts │ └── prop.ts ├── types │ └── index.ts ├── index.ts └── constant.ts ├── tests ├── typescript │ ├── .gitignore │ ├── src │ │ ├── util │ │ │ ├── index.ts │ │ │ └── clone.ts │ │ ├── interfaces │ │ │ └── index.ts │ │ ├── components │ │ │ ├── fruits.ts │ │ │ ├── invalid_component.ts │ │ │ ├── invalid_filter.ts │ │ │ ├── fruits_set_state.ts │ │ │ ├── invalid_event_handler.ts │ │ │ ├── user.ts │ │ │ ├── text-box.ts │ │ │ ├── fragment_comp.ts │ │ │ ├── object_prop.ts │ │ │ ├── component_model.ts │ │ │ ├── btn.ts │ │ │ ├── object_reset_model.ts │ │ │ ├── form.ts │ │ │ ├── array_model.ts │ │ │ ├── users_hobbie.ts │ │ │ ├── tabs.ts │ │ │ ├── textarea_box.ts │ │ │ ├── base_fruits.ts │ │ │ ├── tab_render.ts │ │ │ ├── users.ts │ │ │ ├── computed.ts │ │ │ ├── if_else.ts │ │ │ ├── model.ts │ │ │ ├── fragment.ts │ │ │ ├── directive.ts │ │ │ └── hello_world.ts │ │ ├── index.ts │ │ ├── temp.ts │ │ └── index.html │ ├── package-lock.json │ ├── readme.md │ ├── test │ │ ├── create_component.ts │ │ ├── invalid_formatter.test.ts │ │ ├── standard_button.ts │ │ ├── invalid_event_handler.test.ts │ │ ├── btn_slot_default_content.test.ts │ │ ├── prop_object.test.ts │ │ ├── child_inside_slot.test.ts │ │ ├── invalid_component.test.ts │ │ ├── if_with_not.test.ts │ │ ├── if_initial_value_false.test.ts │ │ ├── btn.test.ts │ │ ├── setup.js │ │ ├── if_not.test.ts │ │ ├── form.test.ts │ │ ├── extend_reactive.test.ts │ │ ├── component_model.test.ts │ │ ├── textarea-box.test.ts │ │ ├── plugin_api.test.ts │ │ ├── textbox.test.ts │ │ ├── index.ts │ │ ├── formatter_error.test.ts │ │ ├── component_load_fail.test.ts │ │ ├── attribute.test.ts │ │ ├── object_prop.test.ts │ │ ├── nested_object_prop.test.ts │ │ ├── array_nested_prop.test.ts │ │ ├── child_comp_destroy.test.ts │ │ ├── tag_list.test.ts │ │ ├── nested_for.test.ts │ │ ├── event.test.ts │ │ ├── array_model.test.ts │ │ ├── users.test.ts │ │ ├── object_reset_model.test.ts │ │ ├── prop_mutate.test.ts │ │ ├── computed.test.ts │ │ ├── tab_render.test.ts │ │ └── in_palce_at_root.test.ts │ ├── tsconfig.json │ ├── webpack.config.test.js │ ├── package.json │ ├── installer.js │ ├── webpack.config.js │ ├── tslint.json │ └── karma.conf.js ├── unit_test │ ├── nested_in_place.test.ts │ ├── package.json │ ├── next_tick.test.ts │ ├── index.ts │ ├── tsconfig.json │ ├── directive_destroy.test.ts │ ├── karma.conf.js │ ├── observer.spec.ts │ ├── watch_decorator.ts │ └── array_nested_elemet_destory.test.ts ├── list │ ├── .gitignore │ ├── config │ │ ├── env │ │ │ ├── development.js │ │ │ ├── production.js │ │ │ └── index.js │ │ ├── lang │ │ │ └── mahal.config.d.ts │ │ └── webpack │ │ │ ├── dev.config.js │ │ │ └── prod.config.js │ ├── src │ │ ├── formatters │ │ │ ├── img_path.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── index.html │ │ ├── utils │ │ │ └── build_data.js │ │ └── components │ │ │ └── mahal_intro.mahal │ ├── assets │ │ └── img │ │ │ └── mahal-logo.png │ ├── build_helper │ │ └── copy_html.js │ ├── index.html │ ├── readme.md │ ├── tsconfig.json │ ├── package.json │ └── test │ │ └── index.js ├── todo │ ├── .gitignore │ ├── config │ │ ├── development.js │ │ ├── production.js │ │ └── index.js │ ├── src │ │ ├── formatters │ │ │ ├── img_path.ts │ │ │ └── index.ts │ │ ├── index.html │ │ ├── index.ts │ │ ├── components │ │ │ ├── todo.mahal │ │ │ └── new_todo.mahal │ │ └── app.mahal │ ├── assets │ │ └── img │ │ │ └── mahal.png │ ├── extra │ │ └── mahal.config.ts │ ├── webpack │ │ ├── dev.config.js │ │ └── prod.config.js │ ├── tsconfig.json │ └── package.json ├── tsconfig.json ├── installer.js ├── install_same_version.js ├── tslint.json └── package.json ├── .gitignore ├── assets └── logo.png ├── .osnft ├── .npmignore ├── .github ├── ISSUE_TEMPLATE │ ├── issue-bug-template.md │ ├── feature-request.md │ └── config.yml └── workflows │ └── ci.yml ├── webpack ├── webpack.test.config.js ├── webpack.base.config.js ├── webpack.dev.config.js └── webpack.prod.config.js ├── tsconfig.json ├── todo.md ├── LICENSE ├── README.md ├── CODING_GUIDELINES.md ├── tslint.json └── CONTRIBUTING.md /src/models/plugin.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | coverage/* -------------------------------------------------------------------------------- /tests/unit_test/nested_in_place.test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | // export * from "./plugin"; -------------------------------------------------------------------------------- /tests/typescript/src/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./clone"; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin 3 | build 4 | *.tgz 5 | 6 | dist -------------------------------------------------------------------------------- /tests/list/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin 3 | dist 4 | build 5 | logs -------------------------------------------------------------------------------- /tests/todo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin 3 | dist 4 | build 5 | logs -------------------------------------------------------------------------------- /tests/todo/config/development.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apiUrl: "/" 3 | } -------------------------------------------------------------------------------- /tests/todo/config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apiUrl: "/" 3 | } -------------------------------------------------------------------------------- /src/abstracts/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./component"; 2 | export * from "./plugin"; -------------------------------------------------------------------------------- /tests/list/config/env/development.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apiUrl: "/" 3 | } -------------------------------------------------------------------------------- /tests/list/config/env/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apiUrl: "/" 3 | } -------------------------------------------------------------------------------- /src/interface/prop_option.ts: -------------------------------------------------------------------------------- 1 | export interface IPropOption { 2 | type: string; 3 | } -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ujjwalguptaofficial/mahal/HEAD/assets/logo.png -------------------------------------------------------------------------------- /src/interface/error.ts: -------------------------------------------------------------------------------- 1 | export interface IError { 2 | type: string; 3 | msg: string; 4 | } -------------------------------------------------------------------------------- /src/utils/is_array.ts: -------------------------------------------------------------------------------- 1 | export const isArray = (value) => { 2 | return Array.isArray(value); 3 | }; -------------------------------------------------------------------------------- /src/utils/merge.ts: -------------------------------------------------------------------------------- 1 | export const merge = (...obj) => { 2 | return Object.assign({}, ...obj); 3 | }; -------------------------------------------------------------------------------- /tests/typescript/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export type IAppGlobal = { 2 | authorName: string 3 | } -------------------------------------------------------------------------------- /src/interface/component_option.ts: -------------------------------------------------------------------------------- 1 | export interface IComponentOption { 2 | props: { 3 | 4 | }; 5 | } -------------------------------------------------------------------------------- /src/utils/get_object_keys.ts: -------------------------------------------------------------------------------- 1 | export const getObjectKeys = (obj) => { 2 | return Object.keys(obj); 3 | }; -------------------------------------------------------------------------------- /tests/list/src/formatters/img_path.ts: -------------------------------------------------------------------------------- 1 | export function imgPath(src: string) { 2 | return "/img/" + src; 3 | } -------------------------------------------------------------------------------- /tests/todo/src/formatters/img_path.ts: -------------------------------------------------------------------------------- 1 | export function imgPath(src: string) { 2 | return "/img/" + src; 3 | } -------------------------------------------------------------------------------- /.osnft: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "walletAddress":"0x0FA4EA6F5A540BA18c477087007098AF954048Be", 4 | "icon":"assets/logo.png" 5 | } 6 | -------------------------------------------------------------------------------- /src/enums/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./error_type"; 2 | export * from "./html_tag"; 3 | export * from "./life_cycle_event"; -------------------------------------------------------------------------------- /src/helpers/create_text_node.ts: -------------------------------------------------------------------------------- 1 | export const createTextNode = (val) => { 2 | return document.createTextNode(val); 3 | }; -------------------------------------------------------------------------------- /src/utils/promise_resolve.ts: -------------------------------------------------------------------------------- 1 | export const promiseResolve = (value) => { 2 | return Promise.resolve(value); 3 | }; -------------------------------------------------------------------------------- /tests/todo/assets/img/mahal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ujjwalguptaofficial/mahal/HEAD/tests/todo/assets/img/mahal.png -------------------------------------------------------------------------------- /src/interface/lazy_component.ts: -------------------------------------------------------------------------------- 1 | export interface ILazyComponent { 2 | isLazy: boolean; 3 | component: Function; 4 | } -------------------------------------------------------------------------------- /src/utils/is_object.ts: -------------------------------------------------------------------------------- 1 | 2 | export const isObject = (value) => { 3 | return value != null && typeof value === 'object'; 4 | }; -------------------------------------------------------------------------------- /tests/list/assets/img/mahal-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ujjwalguptaofficial/mahal/HEAD/tests/list/assets/img/mahal-logo.png -------------------------------------------------------------------------------- /tests/typescript/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsstore_typescript_example", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | tests 4 | webpack 5 | *.tgz 6 | *.env 7 | build 8 | build_helper 9 | 10 | #definition 11 | !dist/ts/src -------------------------------------------------------------------------------- /src/abstracts/plugin.ts: -------------------------------------------------------------------------------- 1 | import { Mahal } from "../mahal"; 2 | 3 | export abstract class Plugin { 4 | abstract setup(app: Mahal, options); 5 | } -------------------------------------------------------------------------------- /src/helpers/create_coment_node.ts: -------------------------------------------------------------------------------- 1 | export const createCommentNode = (text?: string) => { 2 | return document.createComment(text || ""); 3 | }; -------------------------------------------------------------------------------- /tests/list/config/lang/mahal.config.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.mahal" { 2 | import { Component } from "mahal"; 3 | export default Component; 4 | } -------------------------------------------------------------------------------- /src/interface/directive.ts: -------------------------------------------------------------------------------- 1 | export interface IDirective { 2 | inserted?: () => void; 3 | valueUpdated?: () => void; 4 | destroyed?: () => void; 5 | } -------------------------------------------------------------------------------- /src/ready_made/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./model_directive"; 2 | export * from "./ref_directive"; 3 | export * from "./fragment_comp"; 4 | export * from "./event_directive"; -------------------------------------------------------------------------------- /src/utils/replace_null_prop.ts: -------------------------------------------------------------------------------- 1 | export const replaceNullProp = (target: object, prop: string, getValue: () => any) => { 2 | target[prop] = target[prop] || getValue(); 3 | }; -------------------------------------------------------------------------------- /src/enums/life_cycle_event.ts: -------------------------------------------------------------------------------- 1 | export enum LIFECYCLE_EVENT { 2 | Create = "create", 3 | Destroy = "destroy", 4 | Update = "update", 5 | Mount = "mount", 6 | Error = "error" 7 | } -------------------------------------------------------------------------------- /src/utils/hashify_array.ts: -------------------------------------------------------------------------------- 1 | export const hashifyArray = (input: any[]) => { 2 | const obj = {}; 3 | input.forEach(item => { 4 | obj[item] = true; 5 | }); 6 | return obj; 7 | }; -------------------------------------------------------------------------------- /src/utils/get_object_length.ts: -------------------------------------------------------------------------------- 1 | import { getObjectKeys } from "./get_object_keys"; 2 | 3 | export const getObjectLength = (value) => { 4 | return value.length || getObjectKeys(value).length; 5 | }; -------------------------------------------------------------------------------- /tests/list/config/env/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV == 'production') { 2 | module.exports = require('./production.js'); 3 | } 4 | else { 5 | module.exports = require('./development.js'); 6 | } -------------------------------------------------------------------------------- /tests/todo/config/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV == 'production') { 2 | module.exports = require('./production.js'); 3 | } 4 | else { 5 | module.exports = require('./development.js'); 6 | } -------------------------------------------------------------------------------- /src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./prop"; 2 | export * from "./children"; 3 | export * from "./formatter"; 4 | export * from "./reactive"; 5 | export * from "./directive"; 6 | export * from "./computed"; -------------------------------------------------------------------------------- /src/interface/attr_item.ts: -------------------------------------------------------------------------------- 1 | export interface IAttrItem { 2 | v: any; 3 | } 4 | export interface IReactiveAttrItem extends IAttrItem { 5 | k: string[]; 6 | m?: boolean; 7 | rc?: string[]; 8 | } -------------------------------------------------------------------------------- /src/enums/html_tag.ts: -------------------------------------------------------------------------------- 1 | export let tags = []; 2 | 3 | if (process.env.NODE_ENV !== 'production') { 4 | tags = require("html-tags").concat(['slot', 'target']); 5 | } 6 | 7 | export const HTML_TAG = new Set(tags); 8 | -------------------------------------------------------------------------------- /src/utils/for_own.ts: -------------------------------------------------------------------------------- 1 | export const forOwn = (obj: {}, cb: (key, value) => void) => { 2 | for (const key in obj) { 3 | if (obj.hasOwnProperty(key)) { 4 | cb(key, obj[key]); 5 | } 6 | } 7 | }; -------------------------------------------------------------------------------- /src/utils/get_attribute.ts: -------------------------------------------------------------------------------- 1 | export const getAttribute = (element: HTMLElement, key: string) => { 2 | // if (element.nodeType === 8) { 3 | // return null; 4 | // } 5 | return element.getAttribute(key); 6 | }; -------------------------------------------------------------------------------- /tests/list/src/formatters/index.ts: -------------------------------------------------------------------------------- 1 | import { Mahal } from "mahal"; 2 | import { imgPath } from "./img_path"; 3 | 4 | export function registerGlobalFormatter(app: Mahal) { 5 | app.extend.formatter("imgPath", imgPath); 6 | } -------------------------------------------------------------------------------- /tests/todo/src/formatters/index.ts: -------------------------------------------------------------------------------- 1 | import { Mahal } from "mahal"; 2 | import { imgPath } from "./img_path"; 3 | 4 | export function registerGlobalFormatter(app: Mahal) { 5 | app.extend.formatter("imgPath", imgPath); 6 | } -------------------------------------------------------------------------------- /tests/todo/extra/mahal.config.ts: -------------------------------------------------------------------------------- 1 | declare module "*.mahal" { 2 | // import Vue from "vue"; 3 | // export default Vue; 4 | const value: any; // Add better type definitions here if desired. 5 | export default value; 6 | } -------------------------------------------------------------------------------- /src/utils/resolve_path.ts: -------------------------------------------------------------------------------- 1 | export const resolveValue = (path: string, value) => { 2 | if (!path) return value; 3 | const properties = path.split("."); 4 | return properties.reduce((prev, curr) => prev && prev[curr], value); 5 | }; -------------------------------------------------------------------------------- /tests/list/build_helper/copy_html.js: -------------------------------------------------------------------------------- 1 | const { copyFileSync } = require('fs'); 2 | const path = require('path'); 3 | 4 | 5 | copyFileSync( 6 | path.join(__dirname, "../", "dist/index.html"), 7 | path.join(__dirname, "../index.html") 8 | ) -------------------------------------------------------------------------------- /tests/typescript/src/components/fruits.ts: -------------------------------------------------------------------------------- 1 | import { Component, reactive, } from "mahal"; 2 | import BaseFruits from "./base_fruits"; 3 | 4 | 5 | export default class extends BaseFruits { 6 | 7 | @reactive 8 | fruits = []; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/decorators/reactive.ts: -------------------------------------------------------------------------------- 1 | import { replaceNullProp } from "../utils"; 2 | 3 | export const reactive = (target, key: string) => { 4 | const obj = {}; 5 | replaceNullProp(target, '_reactives_', () => obj); 6 | target._reactives_[key] = true; 7 | }; -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type TYPE_RC_STORAGE = Map; 2 | export type TYPE_EVENT_STORE = { 3 | [key: string]: Map 4 | }; 5 | export type TYPE_ALL_LIFE_CYCLE_EVENT = "destroy" | "mount" | "create" | "update" | "error"; -------------------------------------------------------------------------------- /src/helpers/index_of.ts: -------------------------------------------------------------------------------- 1 | export const indexOf = (value, key) => { 2 | let index = -1; 3 | for (const item in value) { 4 | ++index; 5 | if (item === key) { 6 | return index; 7 | } 8 | } 9 | return -1; 10 | }; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./utils"; 2 | export * from "./abstracts"; 3 | export * from "./interface"; 4 | export * from "./decorators" 5 | export * from "./mahal"; 6 | export { TYPE_ALL_LIFE_CYCLE_EVENT } from "./types"; 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/helpers/execute_events.ts: -------------------------------------------------------------------------------- 1 | import { promiseResolve } from "../utils"; 2 | 3 | export function executeEvents(promises, param) { 4 | promises.reduce((p, promise) => { 5 | return p.then(result => promise.call(this, result)); 6 | }, promiseResolve(param)); 7 | } -------------------------------------------------------------------------------- /tests/todo/webpack/dev.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const path = require('path'); 3 | const baseConfig = require('./base.config'); 4 | 5 | module.exports = merge(baseConfig, { 6 | devServer: { 7 | historyApiFallback: true, 8 | }, 9 | }); -------------------------------------------------------------------------------- /tests/typescript/readme.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This is an example of using jsstore with typescript. 4 | 5 | # How to run 6 | 7 | 1. execute `npm install` - install all dependecies 8 | 2. execute `npm run dev` - run the dev server 9 | 3. browse url - `http://localhost:8080/` -------------------------------------------------------------------------------- /src/ready_made/fragment_comp.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "../abstracts/component"; 2 | import { createElement } from "../helpers"; 3 | 4 | export class FragmentComponent extends Component { 5 | render() { 6 | return createElement.call(this, 'slot', []) as any; 7 | } 8 | } -------------------------------------------------------------------------------- /tests/list/config/webpack/dev.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const path = require('path'); 3 | const baseConfig = require('./base.config'); 4 | 5 | module.exports = merge(baseConfig, { 6 | devServer: { 7 | historyApiFallback: true, 8 | }, 9 | }); -------------------------------------------------------------------------------- /tests/typescript/src/components/invalid_component.ts: -------------------------------------------------------------------------------- 1 | import { Component, children, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | 5 | @template(`
6 | 7 |
`) 8 | export default class extends Component { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/interface/directive_binding.ts: -------------------------------------------------------------------------------- 1 | export interface IDirectiveBinding { 2 | input: string; 3 | isComponent: boolean; 4 | // list of dependencies 5 | props: string[]; 6 | value: any; 7 | // raw param provided 8 | params: string[]; 9 | rc?: string[]; 10 | 11 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/invalid_filter.ts: -------------------------------------------------------------------------------- 1 | import { Component, children, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | 5 | @template(`
6 | {{name | invalid}} 7 |
`) 8 | export default class extends Component { 9 | name = "ujjwal" 10 | } 11 | -------------------------------------------------------------------------------- /src/interface/taj_store.ts: -------------------------------------------------------------------------------- 1 | export interface ITajStore { 2 | 3 | getter?; 4 | mutation?; 5 | task?; 6 | state?; 7 | 8 | commit?(mutationName: string, payload?); 9 | 10 | watch?(name: string, cb: (newValue?, oldValue?) => void); 11 | unwatch?(name: string, cb); 12 | 13 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/fruits_set_state.ts: -------------------------------------------------------------------------------- 1 | import BaseFruits from "./base_fruits"; 2 | 3 | 4 | export default class extends BaseFruits { 5 | fruits = []; 6 | 7 | isreactive = false; 8 | 9 | // onInit(): void { 10 | // window['fruits'] = this; 11 | // } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /tests/todo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-bug-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 🐞 3 | about: File a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: ujjwalguptaofficial 7 | 8 | --- 9 | 10 | # Title 11 | 12 | // Add your title here 13 | 14 | # Description 15 | 16 | // Add your description here 17 | -------------------------------------------------------------------------------- /src/utils/for_each.ts: -------------------------------------------------------------------------------- 1 | export const forEach = (obj, cb: (value, key) => void) => { 2 | if (obj.forEach) { 3 | obj.forEach((item, index) => { 4 | cb(item, index); 5 | }); 6 | return; 7 | } 8 | for (const key in obj) { 9 | cb(obj[key], key); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /tests/typescript/src/components/invalid_event_handler.ts: -------------------------------------------------------------------------------- 1 | import { Component, prop, formatter, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 | 6 | `) 7 | 8 | export default class extends Component { 9 | 10 | } -------------------------------------------------------------------------------- /src/helpers/set_component_mount.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "../abstracts"; 2 | import { LIFECYCLE_EVENT } from "../enums"; 3 | 4 | export const setComponentMount = (component: Component, el: HTMLElement) => { 5 | component.element = el; 6 | component.isMounted = true; 7 | component.emit(LIFECYCLE_EVENT.Mount); 8 | }; -------------------------------------------------------------------------------- /src/helpers/get_el_key.ts: -------------------------------------------------------------------------------- 1 | import { MAHAL_KEY } from "../constant"; 2 | 3 | export const getElementKey = (el) => { 4 | return el[MAHAL_KEY]; 5 | }; 6 | 7 | // export const isNodeNotEqual = (el1, el2) => { 8 | // const elKey = getElementKey(el1); 9 | // return elKey == null || elKey !== getElementKey(el2) 10 | // } -------------------------------------------------------------------------------- /src/ready_made/event_directive.ts: -------------------------------------------------------------------------------- 1 | import { IDirectiveBinding } from "../interface"; 2 | import { addEventListener } from "../utils"; 3 | 4 | export const eventDirective = (el: HTMLElement, binding: IDirectiveBinding) => { 5 | const params = binding.params; 6 | addEventListener(el, params[0], params[1] as any, params[2]); 7 | }; 8 | -------------------------------------------------------------------------------- /tests/list/config/webpack/prod.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') 2 | const baseConfig = require('./base.config') 3 | 4 | const prod = merge(baseConfig, { 5 | mode: 'production', 6 | devtool: false, 7 | // output: { 8 | // publicPath: 'dist/', 9 | // }, 10 | }) 11 | 12 | module.exports = prod; -------------------------------------------------------------------------------- /tests/unit_test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unit_test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "karma.conf.js", 6 | "scripts": { 7 | "test": "cross-env NODE_ENV=test karma start karma.conf.js", 8 | "test:prod": "cross-env NODE_ENV=production karma start karma.conf.js" 9 | }, 10 | "author": "", 11 | "license": "ISC" 12 | } -------------------------------------------------------------------------------- /src/decorators/wrap_method_decorator.ts: -------------------------------------------------------------------------------- 1 | export function wrapMethodDecorator(args: any[], executor: Function) { 2 | if (args.length >= 2) { 3 | const [target, propertyName] = args; 4 | executor(target, propertyName, null); 5 | return; 6 | } 7 | return (target, key: string) => { 8 | executor(target, key, args[0]); 9 | }; 10 | } -------------------------------------------------------------------------------- /tests/unit_test/next_tick.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { nextTick} from "mahal"; 3 | 4 | 5 | 6 | describe("Next tick", () => { 7 | 8 | 9 | it("nextTick", async () => { 10 | return new Promise((res) => { 11 | nextTick(_ => { 12 | nextTick(res); 13 | }) 14 | }) 15 | }) 16 | 17 | 18 | 19 | }) -------------------------------------------------------------------------------- /src/interface/render_context.ts: -------------------------------------------------------------------------------- 1 | import { addRc, createElement, createTextNode } from "../helpers"; 2 | 3 | export interface IRenderContext { 4 | createTextNode: typeof createTextNode; 5 | addRc: typeof addRc; 6 | createTextNodeWithRc: Function; 7 | handleExpWithRc: Function; 8 | handleForExpWithRc: Function; 9 | createEl: typeof createElement; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /tests/unit_test/index.ts: -------------------------------------------------------------------------------- 1 | import "./event_bus.test"; 2 | import "./observer.spec"; 3 | import "./object_state.test"; 4 | import "./object_state_set_state.test"; 5 | import "./array_state.test"; 6 | import "./array_state_set_state.test"; 7 | import "./array_nested_elemet_destory.test"; 8 | import "./directive_destroy.test"; 9 | import "./next_tick.test"; 10 | import "./watch_decorator"; -------------------------------------------------------------------------------- /tests/list/index.html: -------------------------------------------------------------------------------- 1 | Mahal
-------------------------------------------------------------------------------- /tests/todo/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Mahal } from "mahal"; 2 | import App from "@/app.mahal"; 3 | import { registerGlobalFormatter } from "@/formatters"; 4 | import config from "~/config"; 5 | 6 | 7 | const app = new Mahal(App, '#app'); 8 | // register global formatter 9 | registerGlobalFormatter(app); 10 | // set config to be available globally 11 | app.global.config = config; 12 | app.create(); -------------------------------------------------------------------------------- /tests/list/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Mahal } from "mahal"; 2 | import App from "@/app.mahal"; 3 | import { registerGlobalFormatter } from "@/formatters"; 4 | import config from "~/config/env"; 5 | 6 | 7 | const app = new Mahal(App, '#main'); 8 | // register global formatter 9 | registerGlobalFormatter(app); 10 | // set config to be available globally 11 | app.global.config = config; 12 | app.create(); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature request \U0001F680" 3 | about: Suggest a feature for Mahal 4 | 5 | --- 6 | 7 | ## Summary 8 | 9 | Brief explanation of the feature. 10 | 11 | ### Basic example 12 | 13 | Include a basic example or links here. 14 | 15 | ### Motivation 16 | 17 | Why are we doing this? What use cases does it support? What is the expected outcome? 18 | -------------------------------------------------------------------------------- /tests/typescript/test/create_component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "mahal"; 2 | import { createRenderer } from "@mahaljs/html-compiler"; 3 | 4 | export function createComponent(template: string, scoped?) { 5 | const result = createRenderer(template, scoped); 6 | 7 | class MyComponent extends Component { 8 | render = result as any; 9 | } 10 | 11 | return MyComponent; 12 | 13 | } -------------------------------------------------------------------------------- /src/helpers/emit_error.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "../abstracts"; 2 | import { LIFECYCLE_EVENT } from "../enums"; 3 | 4 | export function emitError(this: Component, err, shouldLog: boolean) { 5 | if (process.env.NODE_ENV !== 'production' || shouldLog) { 6 | console.error(err); 7 | } 8 | this['_app_'].emit(LIFECYCLE_EVENT.Error, err); 9 | this.emit(LIFECYCLE_EVENT.Error, err); 10 | } -------------------------------------------------------------------------------- /src/interface/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./error"; 2 | export * from "./prop_option"; 3 | export * from "./taj_store"; 4 | export * from "./component_option"; 5 | export * from "./directive_binding"; 6 | export * from "./attr_item"; 7 | export * from "./directive"; 8 | export * from "./render_context"; 9 | export * from "./lazy_component"; 10 | export * from "./error_type"; 11 | export * from "./element_option"; -------------------------------------------------------------------------------- /src/helpers/add_rc.ts: -------------------------------------------------------------------------------- 1 | export function addRc(this: Map>, keys: string[], method: Function) { 2 | keys.forEach(key => { 3 | const val = this.get(key); 4 | if (!val) { 5 | this.set(key, [ 6 | method as any 7 | ]); 8 | } 9 | else { 10 | val.push(method as any); 11 | } 12 | }); 13 | } -------------------------------------------------------------------------------- /src/helpers/subscribe_to_emit_destory.ts: -------------------------------------------------------------------------------- 1 | import { CHILD_DESTROY, EMIT_DESTROY } from "../constant"; 2 | import { addEventListener } from "../utils"; 3 | 4 | export const subscriveToDestroyFromChild = (el: HTMLElement) => { 5 | addEventListener(el, CHILD_DESTROY, (e) => { 6 | if (e.target === el) return; 7 | e.stopImmediatePropagation(); 8 | el[EMIT_DESTROY] = true; 9 | }); 10 | }; -------------------------------------------------------------------------------- /src/ready_made/ref_directive.ts: -------------------------------------------------------------------------------- 1 | import { IDirectiveBinding, IDirective } from "../interface"; 2 | 3 | // tslint:disable-next-line 4 | export const refDirective = function (el: HTMLElement, binding: IDirectiveBinding): IDirective { 5 | const key = binding.props[0]; 6 | this[key] = el; 7 | return { 8 | destroyed: () => { 9 | this[key] = null; 10 | } 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/get_data_type.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from "./is_array"; 2 | 3 | export const getDataype = (value) => { 4 | if (value == null) { 5 | return "null"; 6 | } 7 | const type = typeof value; 8 | switch (type) { 9 | case "object": 10 | if (isArray(value)) { 11 | return "array"; 12 | } 13 | default: 14 | return type; 15 | } 16 | }; -------------------------------------------------------------------------------- /src/decorators/computed.ts: -------------------------------------------------------------------------------- 1 | import { replaceNullProp } from "../utils"; 2 | 3 | export const computed = (...args): MethodDecorator => { 4 | return ((target: any, methodName: string, descriptor: PropertyDescriptor) => { 5 | const obj = {}; 6 | replaceNullProp(target, '_computed_', () => obj); 7 | 8 | target._computed_[methodName] = { args, fn: descriptor.value || descriptor.get }; 9 | }); 10 | }; -------------------------------------------------------------------------------- /src/constant.ts: -------------------------------------------------------------------------------- 1 | export const EL_REPLACED = '_mhlreplaced_'; 2 | export const MAHAL_KEY = '_mhlkey_'; 3 | export const ARRAY_MUTABLE_METHODS = ["push", "pop", "splice", "shift", "unshift", "reverse"] 4 | export const OBJECT_MUTABLE_METHODS = ARRAY_MUTABLE_METHODS.concat(['add', 'update', 'delete']); 5 | export const EMIT_DESTROY = "__emit_destroy__"; 6 | export const CHILD_DESTROY = "__child_destroy__"; 7 | export const emptyObj = Object.freeze({}); -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "es6", 9 | "dom" 10 | ], 11 | "mapRoot": "./", 12 | "module": "es6", 13 | "moduleResolution": "node", 14 | "outDir": "dist/out-tsc", 15 | "sourceMap": true, 16 | "target": "es6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "es6", 9 | "dom" 10 | ], 11 | "mapRoot": "./", 12 | "module": "ES2020", 13 | "moduleResolution": "node", 14 | "outDir": "dist/out-tsc", 15 | "sourceMap": true, 16 | "target": "es6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/unit_test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "es6", 9 | "dom" 10 | ], 11 | "mapRoot": "./", 12 | "module": "ES2020", 13 | "moduleResolution": "node", 14 | "outDir": "dist/out-tsc", 15 | "sourceMap": true, 16 | "target": "es6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/typescript/src/components/user.ts: -------------------------------------------------------------------------------- 1 | import { Component, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 |
6 | User info 7 |
8 | My name is . 9 |
10 |
11 | I am . 12 |
13 |
14 | `) 15 | 16 | export default class extends Component { 17 | 18 | 19 | } -------------------------------------------------------------------------------- /tests/list/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Mahal 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/decorators/children.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "../abstracts"; 2 | import { ILazyComponent } from "../interface"; 3 | 4 | export const children = (value: { [key: string]: typeof Component | Promise | Function | ILazyComponent }) => { 5 | // tslint:disable-next-line 6 | return (constructor: T) => { 7 | return class extends constructor { 8 | children = value; 9 | }; 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/helpers/emit_update.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "../abstracts"; 2 | import { LIFECYCLE_EVENT } from "../enums"; 3 | 4 | const updateEvent = LIFECYCLE_EVENT.Update; 5 | const TIMER_ID = '_timerId_'; 6 | export const emitUpdate = (comp: Component) => { 7 | if (comp.isMounted) { 8 | const id = comp[TIMER_ID]; 9 | clearTimeout(id); 10 | comp[TIMER_ID] = setTimeout(() => { 11 | comp.emit(updateEvent); 12 | }); 13 | } 14 | }; -------------------------------------------------------------------------------- /src/interface/element_option.ts: -------------------------------------------------------------------------------- 1 | import { IAttrItem, IReactiveAttrItem } from "./attr_item"; 2 | import { IDirectiveBinding } from "./directive_binding"; 3 | 4 | export interface IElementOption { 5 | attr?: { [attrName: string]: IAttrItem }; 6 | rAttr?: { [attrName: string]: IReactiveAttrItem }; 7 | rcm?: Function; 8 | on?: { 9 | [eventName: string]: Function 10 | }; 11 | dir?: { 12 | [directiveName: string]: IDirectiveBinding 13 | }; 14 | } -------------------------------------------------------------------------------- /src/interface/error_type.ts: -------------------------------------------------------------------------------- 1 | export interface IErrorType { 2 | InvalidComponent: 'invalid_component'; 3 | InvalidFormatter: "invalid_formatter"; 4 | PropDataTypeMismatch: "prop_data_type_mismatch"; 5 | RendererNotFound: "createRenderer_not_found"; 6 | ForOnPrimitiveOrNull: "for_on_primitive|null"; 7 | InvalidEventHandler: "invalid_event_handler"; 8 | SetSameValue: "set_same_value"; 9 | MutatingProp: "mutating_prop"; 10 | InvalidSlotTarget: "invalid_slot_target"; 11 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/text-box.ts: -------------------------------------------------------------------------------- 1 | import { Component, prop, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 |
6 | Standard Text box 7 | 8 |
9 | `) 10 | 11 | export default class extends Component { 12 | 13 | @prop 14 | value; 15 | 16 | @prop() 17 | textBoxId; 18 | 19 | onInput(e) { 20 | this.emit("input", e.target.value); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/fragment_comp.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "mahal"; 2 | import { IRenderContext } from "mahal/dist/ts/interface"; 3 | 4 | export class FragmentComponent extends Component { 5 | constructor() { 6 | super(); 7 | this.template = `` 8 | } 9 | 10 | render(renderer: IRenderContext): HTMLElement { 11 | const ctx = this; 12 | const ce = this['_createEl_']; 13 | return ce.call(ctx, 'slot', [], {}) as any; 14 | } 15 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/object_prop.ts: -------------------------------------------------------------------------------- 1 | import { children, Component, prop } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 |
6 |

{{val}}

7 |
8 | `) 9 | 10 | export class Test extends Component { 11 | 12 | @prop() 13 | value; 14 | } 15 | 16 | @children({ 17 | Test 18 | }) 19 | @template(` 20 |
21 | 22 |
23 | `) 24 | export default class extends Component { 25 | 26 | } -------------------------------------------------------------------------------- /src/utils/emit_state_change.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "../abstracts"; 2 | 3 | export function emitStateChange(this: Component, key: string, newValue: any, oldValue?: any) { 4 | if (newValue !== oldValue) { 5 | this['_watchBus_'].emitAll(key, newValue, oldValue); 6 | } 7 | // else if (process.env.NODE_ENV !== 'production') { 8 | // if (this['_computed_'][key]) return; 9 | // new Logger(ERROR_TYPE.SetSameValue, { 10 | // // val: oldValue, 11 | // // comp: this, 12 | // key 13 | // }).logWarning(); 14 | // } 15 | } -------------------------------------------------------------------------------- /src/decorators/directive.ts: -------------------------------------------------------------------------------- 1 | import { replaceNullProp } from "../utils"; 2 | import { wrapMethodDecorator } from "./wrap_method_decorator"; 3 | 4 | export function directive(target, key: string): void; 5 | export function directive(name?: string): Function; 6 | export function directive(...args) { 7 | return wrapMethodDecorator(args, createDirective); 8 | } 9 | 10 | function createDirective(target: any, methodName: string, name: string) { 11 | const obj = {}; 12 | replaceNullProp(target, '_directive_', () => obj); 13 | target._directive_[name || methodName] = target[methodName]; 14 | } -------------------------------------------------------------------------------- /src/decorators/formatter.ts: -------------------------------------------------------------------------------- 1 | import { replaceNullProp } from "../utils"; 2 | import { wrapMethodDecorator } from "./wrap_method_decorator"; 3 | 4 | export function formatter(target, key: string): void; 5 | export function formatter(name?: string): Function; 6 | export function formatter(...args) { 7 | return wrapMethodDecorator(args, createFormatter); 8 | } 9 | 10 | function createFormatter(target: any, methodName: string, name: string) { 11 | const obj = {}; 12 | replaceNullProp(target, '_formatters_', () => obj); 13 | target._formatters_[name || methodName] = target[methodName]; 14 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/component_model.ts: -------------------------------------------------------------------------------- 1 | import { Component, reactive, children } from "mahal"; 2 | import TextBox from "./text-box"; 3 | import { template } from "@mahaljs/util"; 4 | 5 | @template(` 6 |
7 | 8 |
9 | `) 10 | @children({ TextBox }) 11 | export default class extends Component { 12 | 13 | @reactive 14 | text = "initial" 15 | 16 | @reactive 17 | id = "txtStandardCheckBox" 18 | 19 | onUpdate() { 20 | this.emit('update'); 21 | } 22 | 23 | onInit(): void { 24 | window['compModel'] = this; 25 | } 26 | } -------------------------------------------------------------------------------- /webpack/webpack.test.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const baseConfig = require('./webpack.base.config'); 3 | const { merge } = require('webpack-merge'); 4 | const webpack = require("webpack"); 5 | 6 | module.exports = [merge(baseConfig[0], { 7 | output: { 8 | path: path.join(__dirname, "../dist"), 9 | filename: "mahal.commonjs2.test.js", 10 | // library: 'mahal', 11 | libraryTarget: "commonjs2", 12 | // libraryExport: 'default' 13 | }, 14 | plugins: [ 15 | new webpack.DefinePlugin({ 16 | 'process.env.NODE_ENV': "'test'", 17 | }) 18 | ] 19 | })] -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "noImplicitAny": false, 5 | "noEmitOnError": true, 6 | "removeComments": false, 7 | "sourceMap": true, 8 | "inlineSources": true, 9 | "moduleResolution": "node", 10 | "target": "es6", 11 | "preserveConstEnums": true, 12 | "declaration": true, 13 | "declarationDir": "./dist/ts", 14 | "module": "es6", 15 | "lib": [ 16 | "es2015", 17 | "dom" 18 | ], 19 | "allowSyntheticDefaultImports": true 20 | }, 21 | "include": [ 22 | "src/*" 23 | ] 24 | } -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | 1. Remove update timer on compoent destroy - done 2 | 2. Fix if if together issue - done 3 | 4 | ``` 5 |
6 |
7 | ``` 8 | 3. Allow tag without end .eg. -
- done 9 | 4. Add if component - 10 | 11 | ``` 12 | 13 | 14 | 15 | 16 | ``` 17 | 5. Allow comment inside comment 18 | 19 | 6. Allow in-place inside for loop 20 | 21 | 7. Allow if on root element 22 | 23 | 8. Fix reactivity issue when using not symbol 24 | 25 | ``` 26 | 27 | Mark as Seen 28 | 29 | ``` -------------------------------------------------------------------------------- /tests/typescript/src/util/clone.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isObject } from "mahal"; 2 | 3 | 4 | export const clone = (obj) => { 5 | if (isObject(obj)) { 6 | // if (isArray(obj)) { 7 | // const copy = []; 8 | // for (const i in obj) { 9 | // copy[i] = obj[i] != null && isObject(obj[i]) ? clone(obj[i]) : obj[i]; 10 | // } 11 | // return copy; 12 | // } 13 | const copy = isArray(obj) ? [] : {}; 14 | for (const i in obj) { 15 | copy[i] = obj[i] != null && isObject(obj[i]) ? clone(obj[i]) : obj[i]; 16 | } 17 | return copy; 18 | } 19 | return obj; 20 | }; -------------------------------------------------------------------------------- /tests/typescript/test/invalid_formatter.test.ts: -------------------------------------------------------------------------------- 1 | import InvalidFilter from "../src/components/invalid_filter"; 2 | import { app } from "../src/index"; 3 | import { expect } from "chai"; 4 | 5 | 6 | describe('Invalid formatter', function () { 7 | 8 | let component; 9 | 10 | it("initiate invalid formatter", async function () { 11 | try { 12 | component = await (app as any).initiate(InvalidFilter); 13 | } catch (error) { 14 | expect(error).equal(`{Mahal throw}: Can not find formatter "invalid". Make sure you have registered formatter either in component or globally.\n\ntype : invalid_formatter`) 15 | } 16 | }); 17 | }); 18 | 19 | -------------------------------------------------------------------------------- /tests/typescript/src/components/btn.ts: -------------------------------------------------------------------------------- 1 | import { Component, prop, formatter, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 | 6 | `) 7 | 8 | export default class extends Component { 9 | 10 | @prop(String) 11 | label; 12 | 13 | handleClick() { 14 | this.emit('click'); 15 | } 16 | 17 | @formatter('toUpper') 18 | toUpper(value) { 19 | return value.toUpperCase(); 20 | } 21 | 22 | constructor() { 23 | super(); 24 | this.on("mount", _ => { 25 | console.log("mounted"); 26 | }) 27 | } 28 | } -------------------------------------------------------------------------------- /tests/typescript/test/standard_button.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 | 14 | `) 15 | export default class Btn extends Component { 16 | onInit() { 17 | this.on("mount", _ => { 18 | console.log("mounted"); 19 | console.log('slot not found in mounted', this.element.querySelector('slot') == null); 20 | console.log('class found in mounted', this.element.classList.contains('btn-slot')); 21 | }) 22 | } 23 | } -------------------------------------------------------------------------------- /src/utils/lazy_component.ts: -------------------------------------------------------------------------------- 1 | import { ILazyComponent } from "../interface"; 2 | import { getDataype } from "./get_data_type"; 3 | 4 | export interface ILazyComponentPayload { 5 | component: () => Promise; 6 | loading: { 7 | component: () => Promise; 8 | delay: number 9 | }; 10 | error: { 11 | component: () => Promise; 12 | }; 13 | timeout: number; 14 | } 15 | export const lazyComponent = (component: Function | ILazyComponentPayload): ILazyComponent => { 16 | if (getDataype(component) === "function") { 17 | return { 18 | isLazy: true, 19 | component: component as any 20 | }; 21 | } 22 | }; -------------------------------------------------------------------------------- /tests/list/readme.md: -------------------------------------------------------------------------------- 1 | # mahal-app 2 | 3 | This app is generated using `mahal-creator`. 4 | 5 | # How to start 6 | 7 | 1. Install node modules by runing command - `npm ci` 8 | 2. Run dev server by runing command - `npm run dev` 9 | 10 | # Commands 11 | 12 | * `npm run dev` - start development server 13 | * `npm run build` - create build for development 14 | * `npm run deploy` - create build for production 15 | 16 | # Folders 17 | 18 | * assets - contains all the static files for the application. 19 | * config - contains configuration files and folders for the application. The **env** folder inside it contains environment configuration. 20 | * src - contains source or code files for the appliction. -------------------------------------------------------------------------------- /src/enums/error_type.ts: -------------------------------------------------------------------------------- 1 | import { IErrorType } from "../interface"; 2 | 3 | export const ERROR_TYPE = { 4 | InvalidFormatter: "invalid_formatter", 5 | PropDataTypeMismatch: "prop_data_type_mismatch", 6 | } as any as IErrorType; 7 | 8 | if (process.env.NODE_ENV !== 'production') { 9 | Object.assign(ERROR_TYPE, { 10 | InvalidComponent: 'invalid_component', 11 | ForOnPrimitiveOrNull: "for_on_primitive|null", 12 | InvalidEventHandler: "invalid_event_handler", 13 | SetSameValue: "set_same_value", 14 | MutatingProp: "mutating_prop", 15 | RendererNotFound: "createRenderer_not_found", 16 | InvalidSlotTarget: "invalid_slot_target" 17 | }); 18 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/object_reset_model.ts: -------------------------------------------------------------------------------- 1 | import { Component, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 |
6 | 7 |
{{student.name}}
8 |
{{student.gender}}
9 | 10 |
11 | `) 12 | 13 | export default class extends Component { 14 | 15 | @reactive 16 | student = { 17 | name: "" 18 | }; 19 | 20 | reset() { 21 | this.student = { 22 | name: "" 23 | }; 24 | } 25 | 26 | onInit(): void { 27 | window['compObj'] = this; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/form.ts: -------------------------------------------------------------------------------- 1 | import { Component, prop, formatter, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(`
5 | 6 | 7 |
8 | `) 9 | 10 | export default class extends Component { 11 | 12 | email = ""; 13 | 14 | validate() { 15 | console.log("vaidate called"); 16 | return /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(this.email); 17 | } 18 | 19 | isValid; 20 | 21 | submit(isValid) { 22 | this.isValid = isValid; 23 | console.log("isValid", isValid, this.email); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/utils/next_tick.ts: -------------------------------------------------------------------------------- 1 | let isExecuting = false; 2 | let callbacks = new Map(); 3 | 4 | const microTaskExecutor = window.queueMicrotask || ((cb: Function) => { 5 | setTimeout(cb, 0); 6 | }); 7 | 8 | const flushCallbacks = () => { 9 | microTaskExecutor(() => { 10 | const copies = callbacks; 11 | callbacks = new Map(); 12 | isExecuting = false; 13 | copies.forEach(cb => { 14 | cb(); 15 | }); 16 | }); 17 | }; 18 | export const nextTick = (cb?: Function): number => { 19 | const id = callbacks.size; 20 | callbacks.set(id, cb); 21 | if (!isExecuting) { 22 | isExecuting = true; 23 | flushCallbacks(); 24 | } 25 | return id; 26 | }; 27 | -------------------------------------------------------------------------------- /tests/typescript/test/invalid_event_handler.test.ts: -------------------------------------------------------------------------------- 1 | import Component from "../src/components/invalid_event_handler"; 2 | import { app } from "../src/index"; 3 | import { expect } from "chai"; 4 | 5 | 6 | describe('Invalid Event Handler', function () { 7 | 8 | let component; 9 | if (process.env.NODE_ENV !== 'production') { 10 | 11 | it("initiate event handler", async function () { 12 | try { 13 | component = await (app as any).initiate(Component); 14 | } catch (error) { 15 | expect(error).equal(`{Mahal throw}: Invalid event handler for event "click", Handler does not exist in component.\n\ntype : invalid_event_handler`); 16 | } 17 | }); 18 | } 19 | 20 | }); 21 | 22 | -------------------------------------------------------------------------------- /tests/typescript/test/btn_slot_default_content.test.ts: -------------------------------------------------------------------------------- 1 | import { nextTick, Component, prop, children, reactive } from "mahal"; 2 | import { expect } from "chai"; 3 | import { template } from "@mahaljs/util"; 4 | import { mount } from "@mahaljs/test-utils"; 5 | import Btn from './standard_button' 6 | 7 | 8 | @children({ 9 | Btn: Btn 10 | }) 11 | @template(` 12 | 13 | `) 14 | class Temp extends Component { 15 | } 16 | 17 | describe('Btn slot default content test', function () { 18 | 19 | let component: Temp; 20 | 21 | it("initiate btn", async function () { 22 | component = await mount(Temp); 23 | const el = component.element; 24 | expect(el.innerText).equal('OK'); 25 | }); 26 | 27 | 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./is_array"; 2 | export * from "./is_object"; 3 | export * from "./next_tick"; 4 | export * from "./get_object_length"; 5 | export * from "./merge"; 6 | export * from "./set_attribute"; 7 | export * from "./for_own"; 8 | export * from "./get_data_type"; 9 | export * from "./event_bus"; 10 | export * from "./get_attribute"; 11 | export * from "./dom"; 12 | export * from "./exeute_render"; 13 | export * from "./init_component"; 14 | export * from "./create_component"; 15 | export * from "./hashify_array"; 16 | export * from "./promise_resolve"; 17 | export * from "./lazy_component"; 18 | export * from "./emit_state_change"; 19 | export * from "./for_each"; 20 | export * from "./resolve_path"; 21 | export * from "./get_object_keys"; 22 | export * from "./replace_null_prop"; -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./observer"; 2 | export * from "./create_coment_node"; 3 | export * from "./execute_events"; 4 | export * from "./create_text_node"; 5 | export * from "./handle_expression"; 6 | export * from "./handle_attribute"; 7 | export * from "./handle_directive"; 8 | export * from "./handle_in_place"; 9 | export * from "./create_element"; 10 | export * from "./emit_update"; 11 | export * from "./comp_clear_all"; 12 | export * from "./emit_error"; 13 | export * from "./logger"; 14 | export * from "./index_of"; 15 | export * from "./get_el_key"; 16 | export * from "./destroy_helper"; 17 | export * from "./subscribe_to_emit_destory"; 18 | export * from "./for_each_event"; 19 | export * from "./add_rc"; 20 | export * from "./set_component_mount"; 21 | export * from "./handle_slot"; 22 | -------------------------------------------------------------------------------- /src/utils/create_component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "../abstracts"; 2 | import { Observer } from "../helpers"; 3 | import { Mahal } from "../mahal"; 4 | import { emitStateChange } from "./emit_state_change"; 5 | import { getObjectKeys } from "./get_object_keys"; 6 | 7 | export const createComponent = (componentConstructor, app: Mahal) => { 8 | let component: Component = new componentConstructor(); 9 | 10 | const keys = getObjectKeys(component['_reactives_']); 11 | component['_app_'] = app; 12 | if (keys.length > 0) { 13 | component = new Observer(emitStateChange.bind(component), component). 14 | create(component, keys) as Component; 15 | } 16 | component['_watchBus_']['_ctx_'] = component; 17 | component['_evBus_']['_ctx_'] = component; 18 | return component; 19 | }; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Ask Question 🙋 3 | url: https://github.com/ujjwalguptaofficial/mahal/discussions/categories/q-a 4 | about: Ask your question in Mahal Discussions 5 | - name: Show & Tell ⚡ 6 | url: https://github.com/ujjwalguptaofficial/mahal/discussions/categories/show-and-tell 7 | about: Show everyone what you have made. This will motivate other users & authors. 8 | - name: Ideas 9 | url: https://github.com/ujjwalguptaofficial/mahal/discussions/categories/ideas 10 | about: Have some idea, let's discuss. 11 | - name: Share article, news or general talk 12 | url: https://github.com/ujjwalguptaofficial/mahal/discussions/categories/general 13 | about: Share anything with us. It might be an article, news or anything which is beneficial for community. -------------------------------------------------------------------------------- /src/helpers/handle_in_place.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "../abstracts"; 2 | import { IElementOption } from "../interface"; 3 | import { createCommentNode } from "./create_coment_node"; 4 | import { createElement } from "./create_element"; 5 | 6 | export function handleInPlace(this: Component, childs, option: IElementOption) { 7 | const of = option.rAttr?.of; 8 | if (process.env.NODE_ENV !== 'production' && !of && option.attr?.of) { 9 | console.warn('Found "of" value as constant, please use component state for setting "of" attribute'); 10 | } 11 | if (!of) return createCommentNode(); 12 | delete option.rAttr.of; 13 | const keys = of.k || []; 14 | return this['_handleExp_'](() => { 15 | return createElement.call(this, (of as any).v, childs, option) as HTMLElement; 16 | }, keys); 17 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/array_model.ts: -------------------------------------------------------------------------------- 1 | import { Component, prop, formatter, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | // @template(`
5 | // 6 | // {{student.name}} 7 | // 8 | //
9 | // `) 10 | @template(`
11 |
12 | 13 | {{fruit}} 14 | 15 |
16 |
17 | `) 18 | 19 | export default class extends Component { 20 | 21 | @reactive 22 | fruits = ["banana", "apple"]; 23 | 24 | updateFruit(fruit, index) { 25 | this.fruits[index] = fruit 26 | // this.setAndReact(this.fruits, index, fruit); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/typescript/webpack.config.test.js: -------------------------------------------------------------------------------- 1 | var nodeExternals = require('webpack-node-externals'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | target: 'node', // webpack should compile node compatible code 6 | resolve: { 7 | extensions: ['.ts'] 8 | }, 9 | module: { 10 | rules: [{ 11 | test: /\.tsx?$/, 12 | use: 'ts-loader', 13 | exclude: /node_modules/ 14 | }, { 15 | test: /\.css?$/, 16 | use: ['style-loader', 'css-loader'], 17 | // exclude: /node_modules/ 18 | }] 19 | }, 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env.NODE_ENV': "'test'" 23 | }) 24 | ], 25 | externals: [nodeExternals()], // in order to ignore all modules in node_modules folder 26 | }; -------------------------------------------------------------------------------- /tests/list/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "es6", 9 | "dom" 10 | ], 11 | "mapRoot": "./", 12 | "module": "es6", 13 | "outDir": "bin/ts", 14 | "sourceMap": true, 15 | "target": "es6", 16 | "moduleResolution": "node", 17 | "paths": { 18 | "~/*": [ 19 | "./*" 20 | ], 21 | "@/*": [ 22 | "./src/*" 23 | ], 24 | "@components/*": [ 25 | "./src/components/*" 26 | ], 27 | "@config/*": [ 28 | "./config/*" 29 | ] 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /tests/todo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "es6", 9 | "dom" 10 | ], 11 | "mapRoot": "./", 12 | "module": "es6", 13 | "outDir": "bin/ts", 14 | "sourceMap": true, 15 | "target": "es6", 16 | "moduleResolution": "node", 17 | "paths": { 18 | "~/*": [ 19 | "./*" 20 | ], 21 | "@/*": [ 22 | "./src/*" 23 | ], 24 | "@components/*": [ 25 | "./src/components/*" 26 | ], 27 | "@config/*": [ 28 | "./config/*" 29 | ] 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/users_hobbie.ts: -------------------------------------------------------------------------------- 1 | import { Component, reactive, prop, children } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 |
Hobbies - {{hobbies}}
6 | `) 7 | class HobbieTemp extends Component { 8 | @prop() hobbies; 9 | } 10 | 11 | @template(` 12 |
13 |
14 |
name - {{user.name}}
15 | 16 |
17 |
18 | `) 19 | @children({ 20 | Hobbie: HobbieTemp 21 | }) 22 | export default class extends Component { 23 | 24 | @reactive users = [{ 25 | name: 'ujjwal', 26 | hobbies: ['travel', 'food', 'code'] 27 | }] 28 | 29 | 30 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/tabs.ts: -------------------------------------------------------------------------------- 1 | import { Component, prop } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | 5 | @template(` 6 |
7 |
8 |
9 | {{tab}} 10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 | `) 18 | export default class extends Component { 19 | 20 | @prop(Array) 21 | tabs = []; 22 | 23 | @prop(String) 24 | value; 25 | 26 | onTabClick(value) { 27 | this.emit('input', value); 28 | } 29 | 30 | onInit(): void { 31 | this.on('mount', () => { 32 | console.log('el', this.element); 33 | }) 34 | } 35 | } -------------------------------------------------------------------------------- /src/helpers/comp_clear_all.ts: -------------------------------------------------------------------------------- 1 | import { LIFECYCLE_EVENT } from "../enums"; 2 | import { Component } from "../abstracts"; 3 | import { dispatchDestroyed } from "./destroy_helper"; 4 | 5 | const destroyEvent = LIFECYCLE_EVENT.Destroy; 6 | export function clearAll(this: Component) { 7 | const ctx = this; 8 | ctx.element['_comp_destroyed_'] = true; 9 | 10 | ctx['_childComps_'].forEach(item => { 11 | // item.emit(destroyEvent); 12 | dispatchDestroyed(item.element); 13 | }); 14 | 15 | // need to emit before clearing events 16 | ctx.emit(destroyEvent).then(_ => { 17 | clearTimeout(ctx['_timerId_']); 18 | ctx['_evBus_'].destroy(); 19 | ctx['_watchBus_'].destroy(); 20 | if (ctx['_ob_']) { 21 | ctx['_ob_'].destroy(); 22 | } 23 | ctx.element = ctx['_evBus_'] = 24 | ctx['_ob_'] = null; 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/decorators/prop.ts: -------------------------------------------------------------------------------- 1 | import { IPropOption } from "../interface"; 2 | import { getDataype, replaceNullProp } from "../utils"; 3 | import { wrapMethodDecorator } from "./wrap_method_decorator"; 4 | 5 | // tslint:disable-next-line 6 | export function prop(target, key: string): void; 7 | export function prop(options?: IPropOption | any): Function; 8 | export function prop(...args) { 9 | return wrapMethodDecorator(args, createProp); 10 | } 11 | 12 | function createProp(target, key: string, options) { 13 | const obj = {}; 14 | replaceNullProp(target, '_props_', () => obj); 15 | if (getDataype(options) === "function") { 16 | const name = options.name; 17 | options = name.toLowerCase(); 18 | } 19 | if (options && !options.type) { 20 | options = { 21 | type: options 22 | }; 23 | } 24 | target._props_[key] = options || {}; 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsstore_typescript_example", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "install:lib": "cd ../ && npm run install:lib", 6 | "install:build": "npm run install:lib && npm run build", 7 | "build": "webpack", 8 | "dev": "webpack-dev-server --config webpack.config.js", 9 | "dev:prod": "webpack-dev-server --config webpack.config.js", 10 | "install:dev": " npm run install:lib && npm run dev", 11 | "install:test": " npm run install:lib && npm run test", 12 | "start": "npm run dev", 13 | "test": "cross-env NODE_ENV=test karma start karma.conf.js", 14 | "test:prod": "cross-env NODE_ENV=production karma start karma.conf.js", 15 | "test:mochapack": "cross-env NODE_ENV=test mochapack --require ./test/setup.js --webpack-config webpack.config.test.js \"test/**/*.test.ts\"" 16 | }, 17 | "dependencies": {}, 18 | "devDependencies": {} 19 | } -------------------------------------------------------------------------------- /tests/typescript/test/prop_object.test.ts: -------------------------------------------------------------------------------- 1 | import ObjectProp from "../src/components/object_prop"; 2 | import { app } from "../src/index"; 3 | import { expect } from "chai"; 4 | import { nextTick } from "mahal"; 5 | 6 | describe('Hardcode Object prop', function () { 7 | 8 | let component; 9 | 10 | before(async function () { 11 | component = await (app as any).initiate(ObjectProp); 12 | }); 13 | 14 | it("check for rendering", function (done) { 15 | nextTick(() => { 16 | const name = component.find('p.name'); 17 | expect(name).to.not.equal(null); 18 | expect(name.innerHTML).to.equal('ujjwal'); 19 | 20 | const gender = component.find('p.gender'); 21 | expect(gender).to.not.equal(null); 22 | expect(gender.innerHTML).to.equal('male'); 23 | 24 | done(); 25 | }) 26 | 27 | }); 28 | 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /tests/todo/src/components/todo.mahal: -------------------------------------------------------------------------------- 1 | --- 2 | name: todo.mahal 3 | description: Render a task with remove button. 4 | events: 5 | remove: emits the task id which can be used to remove target task. 6 | dateCreated: April 24, 2022 7 | --- 8 | 9 | 10 |
11 |
{{task.title}}
12 |
{{task.description}}
13 | 14 |
15 | 16 | 27 | 37 | -------------------------------------------------------------------------------- /tests/typescript/test/child_inside_slot.test.ts: -------------------------------------------------------------------------------- 1 | import { app } from "../src/index"; 2 | import { nextTick, Component, prop, children } from "mahal"; 3 | import { expect } from "chai"; 4 | import { spy } from "sinon"; 5 | import { template } from "@mahaljs/util"; 6 | 7 | 8 | 9 | @children({ 10 | Btn: import('./standard_button') 11 | }) 12 | @template(` 13 |
14 | {{content}} 15 |
16 | `) 17 | class Temp extends Component { 18 | content = "Button" 19 | } 20 | 21 | describe('Btn slot child inside test', function () { 22 | 23 | let component; 24 | 25 | it("initiate btn", async function () { 26 | component = await (app as any).mount(Temp); 27 | const btn: HTMLButtonElement = component.find('button'); 28 | expect(btn.innerHTML).equal(component.content); 29 | expect(btn.classList.contains('btn-slot')).equal(true); 30 | }); 31 | }); 32 | 33 | -------------------------------------------------------------------------------- /tests/typescript/src/components/textarea_box.ts: -------------------------------------------------------------------------------- 1 | import { Component, prop, reactive, } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 |
6 | Standard Text Area box 7 | 8 | textbox value inside component {{value}} 9 |
10 | `) 11 | 12 | export default class extends Component { 13 | 14 | @prop() 15 | value; 16 | 17 | @reactive 18 | text = ""; 19 | 20 | onInit() { 21 | this.on("create", function () { 22 | this.text = this.value; 23 | }) 24 | this.watch("value", this.onValueChange); 25 | this.watch("text", this.onInput); 26 | } 27 | 28 | 29 | onInput(value) { 30 | this.emit("input", value); 31 | } 32 | 33 | onValueChange(value1, value2) { 34 | console.log("value changed", value1, value2); 35 | this.text = value1; 36 | } 37 | 38 | 39 | 40 | } -------------------------------------------------------------------------------- /webpack/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const banner = require('../build_helper/licence'); 3 | const CopyPlugin = require('copy-webpack-plugin'); 4 | const webpack = require('webpack'); 5 | 6 | module.exports = [{ 7 | name: "mahal", 8 | entry: "./src/index.ts", 9 | module: { 10 | rules: [{ 11 | test: /\.ts$/, 12 | exclude: /node_modules/, 13 | use: { 14 | loader: 'ts-loader' 15 | } 16 | }] 17 | }, 18 | mode: 'none', 19 | resolve: { 20 | extensions: ['.ts', '.js'] // '' is needed to find modules like "jquery" 21 | }, 22 | plugins: [ 23 | new webpack.BannerPlugin(banner), 24 | new CopyPlugin({ 25 | patterns: [ 26 | { from: 'build_helper/npm.export.js', to: '' }, 27 | ], 28 | }), 29 | ], 30 | 31 | // optimization: { 32 | // nodeEnv: false 33 | // }, 34 | }]; -------------------------------------------------------------------------------- /tests/typescript/installer.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require('fs'); 2 | const { execSync } = require('child_process'); 3 | const path = require('path'); 4 | 5 | const content = readFileSync("../../package.json"); 6 | 7 | const packageInfo = JSON.parse(content); 8 | 9 | const compilerFolderLocation = path.join(__dirname, "../../../mahal-html-compiler"); 10 | console.log("folderLocation", compilerFolderLocation); 11 | const compilerContent = readFileSync(`${compilerFolderLocation}/package.json`); 12 | const compilerPackageInfo = JSON.parse(compilerContent); 13 | 14 | if (packageInfo) { 15 | const version = packageInfo.version; 16 | console.log('version', version); 17 | execSync(`npm i ../../mahal-${version}.tgz`); 18 | execSync(`npm i ${compilerFolderLocation}/mahal-html-compiler-${compilerPackageInfo.version}.tgz`); 19 | execSync(`npm i ../../../@mahaljs/test-utils/@mahaljs/test-utils-0.1.0.tgz --no-save`); 20 | } 21 | else { 22 | throw "no package found"; 23 | } -------------------------------------------------------------------------------- /tests/typescript/src/components/base_fruits.ts: -------------------------------------------------------------------------------- 1 | import { Component, reactive } from "mahal"; 2 | import { clone } from "../util"; 3 | import { template } from "@mahaljs/util"; 4 | 5 | @template(` 6 |
7 |

8 | {{index}}-{{fruit}} 9 |

10 |
11 | `) 12 | export default class extends Component { 13 | 14 | fruits; 15 | 16 | get initialFruits() { 17 | return ["Banana", "Orange", "Apple", "Mango"]; 18 | } 19 | 20 | onInit() { 21 | window['fruitsComp'] = this; 22 | this.setInitial(); 23 | this.on("update", () => { 24 | console.log('updated'); 25 | }); 26 | } 27 | 28 | setInitial() { 29 | const newFruits = clone(this.initialFruits); 30 | if (this.isreactive) { 31 | this.fruits = newFruits; 32 | } 33 | else { 34 | this.setState('fruits', newFruits); 35 | } 36 | } 37 | 38 | isreactive = true; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /tests/list/src/utils/build_data.js: -------------------------------------------------------------------------------- 1 | function random(max) { 2 | return Math.round(Math.random() * 1000) % max; 3 | } 4 | 5 | const A = ["pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain", "quaint", "clean", 6 | "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd", "unsightly", "adorable", "important", "inexpensive", 7 | "cheap", "expensive", "fancy"]; 8 | const C = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"]; 9 | const N = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", 10 | "keyboard"]; 11 | 12 | let nextId = 1; 13 | 14 | export function buildData(count) { 15 | const data = new Array(count); 16 | for (let i = 0; i < count; i++) { 17 | data[i] = { 18 | id: nextId++, 19 | label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`, 20 | }; 21 | } 22 | return data; 23 | } -------------------------------------------------------------------------------- /src/helpers/for_each_event.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "../abstracts"; 2 | import { ERROR_TYPE } from "../enums"; 3 | import { executeEvents } from "./execute_events"; 4 | import { Logger } from "./logger"; 5 | 6 | export const forEachEvent = function (this: Component, events: { [key: string]: Function[] }, cb: (event: string, listener: Function) => void) { 7 | if (!events) return; 8 | for (const eventName in events) { 9 | const methods = events[eventName]; 10 | if (process.env.NODE_ENV !== 'production') { 11 | methods.forEach(item => { 12 | if (typeof item !== 'function') { 13 | new Logger(ERROR_TYPE.InvalidEventHandler, { 14 | ev: eventName, 15 | }).throwPlain(); 16 | } 17 | }); 18 | } 19 | const eventListener = methods.length === 1 ? methods[0].bind(this) : (e) => { 20 | executeEvents.call(this, methods, e); 21 | }; 22 | cb(eventName, eventListener); 23 | } 24 | }; -------------------------------------------------------------------------------- /tests/typescript/src/components/tab_render.ts: -------------------------------------------------------------------------------- 1 | import { Component, children, reactive } from "mahal"; 2 | import Users from "./users"; 3 | import Tabs from "./tabs"; 4 | import { template } from "@mahaljs/util"; 5 | 6 | @template(`
7 |
{{activeTab}}
8 | 9 | 10 | 11 | 12 | 13 |
`) 14 | @children({ 15 | Users, Tabs 16 | }) 17 | export default class extends Component { 18 | 19 | tabs = ["Users", "Btn"]; 20 | 21 | @reactive 22 | activeTab = "Btn"; 23 | 24 | users = [{ 25 | name: "Ujjwal kumar", 26 | gender: "Male" 27 | }] 28 | 29 | items = ["hello", "world"] 30 | 31 | onInit() { 32 | window['tabComp'] = this; 33 | this.watch("activeTab", (value) => { 34 | console.log(value); 35 | }) 36 | this.on("update", () => { 37 | console.log('updated'); 38 | }) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /tests/typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Mahal } from "mahal"; 2 | import Main from "./components/main"; 3 | import Btn from "./components/btn"; 4 | import MahalTest from "@mahaljs/test-utils"; 5 | import { createRenderer } from "@mahaljs/html-compiler"; 6 | import { toString } from "@mahaljs/util"; 7 | 8 | if (process.env.BUILD_ENV != "test") { 9 | require("flexboot"); 10 | } 11 | 12 | 13 | export const app = new Mahal(Main as any, document.querySelector('#app')); 14 | app.global.authorName = "ujjwal"; 15 | app.extend.formatter("dollar", (value: string) => { 16 | return "$" + value; 17 | }); 18 | app.extend.component("Btn", Btn); 19 | app.extend.formatter("toS", toString); 20 | // app.extend.component("fragment", FragmentComponent); 21 | // (Mahal as any).createRenderer = createRenderer; 22 | app.extend.setRenderer(createRenderer); 23 | window['app'] = app; 24 | if (process.env.BUILD_ENV !== "test") { 25 | app.create().catch(err => { 26 | console.log("err", err) 27 | }) 28 | } 29 | else { 30 | app.extend.plugin(MahalTest, app); 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/typescript/src/components/users.ts: -------------------------------------------------------------------------------- 1 | import { Component, prop, children, reactive } from "mahal"; 2 | import User from "./user"; 3 | import { template } from "@mahaljs/util"; 4 | 5 | @template(` 6 |
7 | 8 | {{user.name}} 9 | {{user.gender}} 10 | 11 | 12 | {{user.name}} 13 | {{user.gender}} 14 | 15 |
16 | `) 17 | @children({ 18 | User 19 | }) 20 | 21 | export default class extends Component { 22 | 23 | @prop(Array) 24 | users = [{ 25 | name: "Ujjwal", 26 | gender: "Male" 27 | }] 28 | 29 | @reactive 30 | reactiveUsers = [{ 31 | name: "Ujjwal", 32 | gender: "Male" 33 | }] 34 | 35 | onInit() { 36 | window['compUsers'] = this; 37 | this.on("update", () => { 38 | console.log("updated"); 39 | }) 40 | } 41 | 42 | 43 | } -------------------------------------------------------------------------------- /tests/typescript/test/invalid_component.test.ts: -------------------------------------------------------------------------------- 1 | import InvalidComponent from "../src/components/invalid_component"; 2 | import { app } from "../src/index"; 3 | import { expect } from "chai"; 4 | 5 | 6 | describe('Invalid Component', function () { 7 | 8 | 9 | it("initiate invalid component", function (done) { 10 | 11 | const promise = (app as any).initiate(InvalidComponent); 12 | if (process.env.NODE_ENV === 'production') { 13 | promise.then(component => { 14 | const el: HTMLElement = component.element; 15 | expect(el.tagName).equal('DIV'); 16 | expect(el.querySelectorAll('IfElse')).length(1); 17 | done(); 18 | }) 19 | } 20 | else { 21 | promise.catch(error => { 22 | expect(error).equal('{Mahal throw}: Component "IfElse" is not registered. Make sure you have registered component either in parent component or globally.\n\ntype : invalid_component') 23 | done(); 24 | }) 25 | } 26 | }); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /tests/typescript/src/components/computed.ts: -------------------------------------------------------------------------------- 1 | import { Component, computed, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 |
6 | {{fullName}} 7 |
8 | `) 9 | export default class extends Component { 10 | 11 | @reactive 12 | firstName = "ujjwal"; 13 | 14 | @reactive 15 | lastName = "gupta"; 16 | 17 | @computed("firstName", "lastName") 18 | get fullName() { 19 | return this.firstName + " " + this.lastName; 20 | } 21 | 22 | constructor() { 23 | super(); 24 | this.on("mount", function () { 25 | console.log("mounted", this); 26 | }) 27 | window['comp'] = this; 28 | } 29 | 30 | gendergetCounter = 0; 31 | @reactive gender = "male"; 32 | 33 | @computed("gender") 34 | get genderDetail() { 35 | this.gendergetCounter++; 36 | return `I am ${this.gender}`; 37 | } 38 | 39 | @computed("gender") 40 | genderDetailCopy() { 41 | return `I am ${this.gender}`; 42 | } 43 | 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /tests/typescript/test/if_with_not.test.ts: -------------------------------------------------------------------------------- 1 | import { app } from "../src/index"; 2 | import { nextTick, Component, prop, children, formatter, reactive } from "mahal"; 3 | import { expect } from "chai"; 4 | import { template } from "@mahaljs/util"; 5 | 6 | 7 | @children({ 8 | }) 9 | @template(` 10 |
11 |
0
12 |
1
13 |
14 | `) 15 | class Temp extends Component { 16 | 17 | @reactive 18 | state = 0; 19 | } 20 | 21 | describe('If With Not', function () { 22 | 23 | let component: Temp; 24 | 25 | it("initiate", async function () { 26 | component = await (app as any).mount(Temp); 27 | }); 28 | 29 | it("check for initial data", async function () { 30 | const btn = component.find('div'); 31 | expect(btn.innerHTML).equal('0'); 32 | }); 33 | 34 | it("after making state to 1", async function () { 35 | component.state = 1; 36 | await component.waitFor("update"); 37 | const btn = component.find('div'); 38 | expect(btn.innerHTML).equal('1'); 39 | }); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /tests/typescript/test/if_initial_value_false.test.ts: -------------------------------------------------------------------------------- 1 | import { app } from "../src/index"; 2 | import { nextTick, Component, prop, children, reactive } from "mahal"; 3 | import { expect } from "chai"; 4 | import { template } from "@mahaljs/util"; 5 | 6 | @template(` 7 |
8 |
9 | `) 10 | export class Div extends Component { 11 | 12 | cond = false; 13 | } 14 | 15 | @template(` 16 | 17 | `) 18 | export class InPlace extends Component { 19 | 20 | name; 21 | } 22 | 23 | 24 | describe('if Initial value false', function () { 25 | 26 | it("initiate Div", async function () { 27 | const component = await (app as any).mount(Div); 28 | 29 | // should be 8 for future 30 | expect(component.element.nodeType).equal(1); 31 | }); 32 | 33 | it("initiate inPlace", async function () { 34 | const component = await (app as any).mount(InPlace); 35 | 36 | // should be 8 for future 37 | expect(component.element.nodeType).equal(8); 38 | expect(component.find('.temp')).equal(undefined); 39 | }); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ujjwal Gupta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const baseConfig = require('./webpack.base.config'); 3 | const { merge } = require('webpack-merge'); 4 | const webpack = require("webpack"); 5 | 6 | const libraryTarget = [{ 7 | type: "var", 8 | name: 'mahal.js' 9 | }, { 10 | type: "commonjs2", 11 | name: 'mahal.commonjs2.js' 12 | }]; 13 | 14 | function getConfigForTaget(target) { 15 | return { 16 | devtool: 'source-map', 17 | output: { 18 | path: path.join(__dirname, "../dist"), 19 | filename: target.name, 20 | library: target.type === 'var' ? 'mahal' : undefined, 21 | libraryTarget: target.type 22 | }, 23 | plugins: [ 24 | new webpack.DefinePlugin({ 25 | 'process.env.NODE_ENV': "'development'", 26 | }) 27 | ] 28 | } 29 | } 30 | 31 | function createConfigsForAllLibraryTarget() { 32 | var configs = []; 33 | libraryTarget.forEach(function (target) { 34 | configs.push(merge(baseConfig[0], getConfigForTaget(target))); 35 | }) 36 | return configs; 37 | } 38 | 39 | module.exports = [...createConfigsForAllLibraryTarget()] -------------------------------------------------------------------------------- /tests/typescript/src/temp.ts: -------------------------------------------------------------------------------- 1 | // (function anonymous(ce, ct, f, he, hForE 2 | // ) { 3 | // const ctx = this; 4 | // return ce('div', [ce('div', [ct('toggle'),], { 5 | // on: { 6 | // click: ctx.toggleFlag 7 | // }, 8 | // attr: { 9 | // name: { 10 | // v: ctx.name, 11 | // k: 'name' 12 | // } 13 | // } 14 | // }), ce('div', [ct('SHowHide'),], { 15 | // dir: { 16 | // show: { 17 | // value: () => { 18 | // return ctx.flag 19 | // }, 20 | // props: ['flag'] 21 | // } 22 | // }, 23 | // } 24 | // }), ce('div', [he(() => { 25 | // return ct(ctx.name) 26 | // }, ['name'], 1),], { 27 | // on: { 28 | // click: ctx.onClick 29 | // } 30 | // }), ce('TextBox', [], { 31 | // dir: { 32 | // model: { 33 | // value: () => { 34 | // return ctx.name 35 | // }, 36 | // props: ['name'] 37 | // } 38 | // }, 39 | // }), ce('ObjectComponent', [], {}), ], { }) 40 | // }) -------------------------------------------------------------------------------- /tests/typescript/test/btn.test.ts: -------------------------------------------------------------------------------- 1 | import Btn from "../src/components/btn"; 2 | import { app } from "../src/index"; 3 | import { expect } from "chai"; 4 | import { spy } from "sinon"; 5 | 6 | describe('Btn prop test', function () { 7 | 8 | let component; 9 | 10 | it("initiate btn with wrong data type", async function () { 11 | const consoleSpy = spy(console, "error"); 12 | component = await (app as any).mount(Btn, { 13 | props: { 14 | label: false 15 | } 16 | }); 17 | const args = consoleSpy.args[0]; 18 | expect(args).length(2); 19 | expect(args[0]).to.equal("{Mahal error}:"); 20 | expect(args[1]).to.equal(' Expected datatype of property label is string but received boolean.\n\n\n\ntype : prop_data_type_mismatch'); 21 | consoleSpy.restore(); 22 | }); 23 | 24 | 25 | it("initiate with right data type", async function () { 26 | component = await (app as any).mount(Btn, { 27 | props: { 28 | label: "ujjwal" 29 | } 30 | }); 31 | const data = component.element.textContent.trim(); 32 | expect(data).equal("UJJWAL"); 33 | }); 34 | 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /tests/todo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mahal-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack serve --open --config webpack/dev.config.js", 8 | "deploy": "cross-env NODE_ENV=production webpack --config webpack/prod.config.js", 9 | "build": "cross-env NODE_ENV=development webpack --config webpack/dev.config.js" 10 | }, 11 | "author": "", 12 | "license": "", 13 | "private": "true", 14 | "dependencies": { 15 | "flexstyle": "^1.0.3" 16 | }, 17 | "devDependencies": { 18 | "clean-webpack-plugin": "^4.0.0", 19 | "copy-webpack-plugin": "^10.2.4", 20 | "cross-env": "^7.0.3", 21 | "css-loader": "^6.5.1", 22 | "css-minimizer-webpack-plugin": "^3.4.1", 23 | "html-webpack-plugin": "^5.2.0", 24 | "mahal-webpack-loader": "^1.1.1", 25 | "mini-css-extract-plugin": "^2.5.3", 26 | "sass": "^1.49.0", 27 | "sass-loader": "^12.4.0", 28 | "style-loader": "^3.3.1", 29 | "ts-loader": "^9.2.6", 30 | "typescript": "^4.5.5", 31 | "webpack": "^5.67.0", 32 | "webpack-cli": "^4.9.2", 33 | "webpack-dev-server": "^4.7.3", 34 | "webpack-merge": "^5.8.0" 35 | }, 36 | "project": { 37 | "framework": "mahal" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/typescript/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | const webpack = require("webpack"); 4 | module.exports = { 5 | entry: path.join(__dirname, './src/index.ts'), 6 | devtool: 'source-map', 7 | module: { 8 | rules: [{ 9 | test: /\.tsx?$/, 10 | use: 'ts-loader', 11 | exclude: /node_modules/ 12 | }, { 13 | test: /\.css?$/, 14 | use: ['style-loader', 'css-loader'], 15 | // exclude: /node_modules/ 16 | }] 17 | }, 18 | resolve: { 19 | extensions: ['.tsx', '.ts', '.js', '.css'] 20 | }, 21 | output: { 22 | filename: 'bundle.js', 23 | path: path.resolve(__dirname, 'bin/') 24 | }, 25 | plugins: [ 26 | new webpack.DefinePlugin({ 27 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 28 | }), 29 | new HtmlWebPackPlugin({ 30 | cache: true, 31 | hash: true, 32 | template: path.join(__dirname, './src/index.html'), 33 | minify: { 34 | collapseWhitespace: true, 35 | removeComments: true, 36 | removeRedundantAttributes: true, 37 | removeScriptTypeAttributes: true, 38 | removeStyleLinkTypeAttributes: true 39 | } 40 | }) 41 | ] 42 | }; -------------------------------------------------------------------------------- /tests/typescript/src/components/if_else.ts: -------------------------------------------------------------------------------- 1 | import { Component, prop, formatter, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 |
6 |
0th{{state}}
7 |
1st{{state}}
8 |
{{state | dollar}}
9 | 10 | =4 && state 11 | 12 |
{{state}}
13 |
14 | `) 15 | 16 | export default class extends Component { 17 | 18 | @reactive 19 | nested = { 20 | nested1: { 21 | nested2: { 22 | nested3: 10 23 | } 24 | } 25 | } 26 | 27 | @reactive 28 | state; 29 | 30 | @reactive 31 | anotherState = 10; 32 | 33 | name = "ujjwal" 34 | 35 | onInit() { 36 | window["ifcomp"] = this; 37 | 38 | this.on("update", this.updated); 39 | } 40 | 41 | updated() { 42 | console.info("updated"); 43 | } 44 | } -------------------------------------------------------------------------------- /tests/typescript/test/setup.js: -------------------------------------------------------------------------------- 1 | const jsdom = require('jsdom'); 2 | const { expect } = require("chai"); 3 | 4 | 5 | // console.log(process.env.NODE_ENV) 6 | // global.expect = expect; 7 | const jsdomInstance = new jsdom.JSDOM(` 8 |
`); 9 | global.window = jsdomInstance.window; 10 | global.document = window.document; 11 | window.console = global.console; 12 | 13 | Object.keys(document.defaultView).forEach((property) => { 14 | if (typeof global[property] === 'undefined') { 15 | global[property] = document.defaultView[property]; 16 | } 17 | }); 18 | 19 | global.navigator = { 20 | userAgent: 'node.js' 21 | }; 22 | 23 | 24 | window.onerror = function (message, source, lineno, colno, error) { 25 | window.error = message; 26 | }; 27 | 28 | window.onunhandledrejection = function (message) { 29 | window.error = message; 30 | }; 31 | 32 | const virtualConsole = jsdom.createVirtualConsole(); 33 | virtualConsole.on('error', (...errors) => { 34 | console.log("errors caught at", errors) 35 | }); 36 | 37 | process.on('unhandledRejection', (reason, p) => { 38 | console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); 39 | // application specific logging, throwing an error, or other logic here 40 | }); -------------------------------------------------------------------------------- /tests/installer.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require('fs'); 2 | const { execSync } = require('child_process'); 3 | const path = require('path'); 4 | 5 | const content = readFileSync("../package.json"); 6 | 7 | const packageInfo = JSON.parse(content); 8 | 9 | // const compilerFolderLocation = path.join(__dirname, "../../mahal-html-compiler"); 10 | // console.log("folderLocation", compilerFolderLocation); 11 | // const compilerContent = readFileSync(`${compilerFolderLocation}/package.json`); 12 | // const compilerPackageInfo = JSON.parse(compilerContent); 13 | 14 | // const testUtilsFolderLocation = path.join(__dirname, "../../@mahaljs/test-utils"); 15 | // console.log("testUtilsFolderLocation", testUtilsFolderLocation); 16 | // const testUtilsContent = readFileSync(`${testUtilsFolderLocation}/package.json`); 17 | // const testUtilsPackageInfo = JSON.parse(testUtilsContent); 18 | 19 | // execSync(`npm i ${compilerFolderLocation}/mahal-html-compiler-${compilerPackageInfo.version}.tgz`); 20 | // execSync(`npm i ../../@mahaljs/test-utils/@mahaljs/test-utils-${testUtilsPackageInfo.version}.tgz`); 21 | 22 | if (packageInfo) { 23 | const version = packageInfo.version; 24 | console.log('version', version); 25 | execSync(`npm i ../mahal-${version}.tgz --no-save`); 26 | 27 | } 28 | else { 29 | throw "no package found"; 30 | } -------------------------------------------------------------------------------- /tests/typescript/test/if_not.test.ts: -------------------------------------------------------------------------------- 1 | import { app } from "../src/index"; 2 | import { nextTick, Component, prop, children, formatter, reactive } from "mahal"; 3 | import { expect } from "chai"; 4 | import { template } from "@mahaljs/util"; 5 | 6 | 7 | @children({ 8 | }) 9 | @template(` 10 |
11 |
Ujjwal
12 |
13 | `) 14 | class Temp extends Component { 15 | 16 | @reactive 17 | flagOne = false; 18 | } 19 | 20 | describe('If not', function () { 21 | 22 | let component: Temp; 23 | 24 | it("initiate", async function () { 25 | component = await (app as any).mount(Temp); 26 | }); 27 | 28 | it("check for initial data", async function () { 29 | const btn = component.element 30 | expect(btn.innerText).equal('Ujjwal'); 31 | 32 | const flagOneEl = component.find('.flag-one'); 33 | expect(flagOneEl.innerText).equal('Ujjwal'); 34 | }); 35 | 36 | it("after making flagone to true", async function () { 37 | component.flagOne = true; 38 | await component.waitFor("update"); 39 | 40 | const btn = component.element 41 | expect(btn.innerText).equal(''); 42 | 43 | const flagOneEl = component.find('.flag-one'); 44 | expect(flagOneEl).equal(null); 45 | 46 | }); 47 | }); 48 | 49 | -------------------------------------------------------------------------------- /tests/typescript/test/form.test.ts: -------------------------------------------------------------------------------- 1 | import FormComponent from "../src/components/form"; 2 | import { app } from "../src/index"; 3 | import { nextTick } from "mahal"; 4 | import { expect } from "chai"; 5 | 6 | describe('Form', function () { 7 | 8 | let component; 9 | 10 | before(async function () { 11 | component = await (app as any).initiate(FormComponent); 12 | }); 13 | 14 | it("set email value from comp", function (done) { 15 | component.email = "hi there"; 16 | nextTick(() => { 17 | expect(component.find('input').value).equal(""); 18 | done(); 19 | }) 20 | }) 21 | 22 | it("set textbox to invalid email", function (done) { 23 | const input = component.find('input'); 24 | input.setValue("random"); 25 | component.find("#btnSubmit").click(); 26 | setTimeout(() => { 27 | expect(component.isValid).equal(false); 28 | done(); 29 | }, 10) 30 | }); 31 | 32 | it("set textbox to valid email", function (done) { 33 | const input = component.find('input'); 34 | input.setValue("random@gmail.com"); 35 | component.find("#btnSubmit").click(); 36 | setTimeout(() => { 37 | expect(component.isValid).equal(true); 38 | done(); 39 | }, 10); 40 | }); 41 | 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /tests/typescript/src/components/model.ts: -------------------------------------------------------------------------------- 1 | import { Component, reactive } from "mahal"; 2 | import { template } from "@mahaljs/util"; 3 | 4 | @template(` 5 |
6 | 7 |