Web app that splits a URL graphically I wrote this web app that splits a URL graphically so that its parts can be customized to create a new URL. https://github.com/ibrahim-islam/urlpad. The app currently works as expected for usual URls but I am not so sure about the code structure.

module UrlPad {

    const map = (o: OmniboxFull): CustomOmniboxMap =&gt; {
        return {
            protocol: o.protocol,
            host: o.host,
            port: o.port,
            pathnames: o.pathname.split('/').filter(char =&gt; char),
            hash: o.hash,
            query: o.query
        };
    }

    class FormBuilder {

        private template: string[] = [];
        private for: string = '';

        constructor(forClass?: string) {
            this.for = forClass;
            this.template.push('&lt;div class="form-group"&gt;');
        }

        label(title: string) {
            this.template.push(`&lt;label class="col-sm-2"&gt;${title}&lt;/label&gt;`);
            return this;
        }

        input(value: string, id?: string, size: number = 10) {
            let id_att = id ? 'id="'+ id +'"' : '';
            this.template.push(`&lt;div class="col-sm-${size}"&gt;
            &lt;input ${id_att} spellcheck="false" type="text" class="form-control ${this.for}" value="${value}"&gt;&lt;/div&gt;`);
            return this;
        }

        build() {
            this.template.push('&lt;/div&gt;')
            return this.template.join('');
        }
    }

    export function parse(url: string): string {
        let o = map(omnibox.parse(url));

        return Object.keys(o)
            .filter((key, index) =&gt; o[key])
            .map((key, index) =&gt; {
                let value = o[key];
                switch (key) {
                    case 'query':
                        return Object.keys(value)
                            .map((key, index) =&gt; new FormBuilder('query').label(`Querystring #${index}`).input(key, '',  5).input(value[key], '', 5).build())
                            .join('');

                    case 'pathnames':
                        return value
                            .map((value, index) =&gt; new FormBuilder('path').label(`Pathname #${index}`).input(value).build())
                            .join('');

                    default:
                        return new FormBuilder().label(key).input(value, key).build();
                }
            })
            .join('');
    }

    export function make(o: CustomOmniboxMap): string {
        let querystrings = Object.keys(o.query)
            .map((key, index) =&gt; `${key}=${o.query[key]}`)
            .join('&amp;'),
            paths = o.pathnames.join('/');

        return `${o.protocol}://${o.host}${o.port ? ':' + o.port : ''}/${paths}${querystrings ? '?' + querystrings : ''}${o.hash ? o.hash : ''}`;
    }

}

window["UrlPad"] = UrlPad;



I made the app related functionality into a module, should it have been a class?
map function is only used inside parse function, should it have been a local function?
When testing this app should I test only the exported functions or any class or function I wrote as well?


Any other observations or suggestions to improve the code is also welcomed.
For the most part, this looks good. However, there are a few things I'd recommend changing. 


Don't use module. namespace should be used instead. From the Typescript Handbook:


  A note about terminology: It’s important to note that in TypeScript 1.5, the nomenclature has changed. “Internal modules” are now “namespaces”. “External modules” are now simply “modules”, as to align with ECMAScript 2015’s terminology, (namely that module X { is equivalent to the now-preferred namespace X {).

You do not properly escape special characters. If a double quote appears anywhere in the URL, the form will break. Try the url https://example.com/?a=" &gt;Broke to see an example.
Related to the above, building HTML with strings is an incredibly error prone process. If at all possible, avoid it. It is a better idea to use the &lt;template&gt; tag.
You don't need omnibox browsers have a built in URL api which covers everything that omnibox does and more. If you need to support IE, you can use the url-polyfill package - and gain the advantage that newer browsers don't need to load it.
Consider using Object.entries instead of Object.keys as it can slightly simplify some of your code. 

const querystrings = Object.entries(o.query)
    .map(([key, value]) =&gt; `${key}=${value}`)
    .join('&amp;')



For your specific questions:


A module namespace is a reasonable choice for the app. I personally never use them, preferring to use ES Modules. A larger concern is that you export the module to the global scope unnecessarily. Instead of using onclick="parse()" in your HTML, it would be better to add the event handler in your index.ts file and have this module in it's own file.
I don't believe you even need the map function, it wouldn't be that big of a deal to check for pathname instead of pathnames in the switch statement and split the string there. If you do keep the map function, it should be renamed to something more descriptive. When I saw map I expected it to be roughly equivalent to Array.prototype.map. 
For tests, it depends on who you ask. Since you aren't parsing the URL yourself (good!) there isn't really too much that can go wrong here. I'd probably only write a few integration tests for this app as a whole.