How to extract fetch headers without awaiting a request in the browser console?

I am working in an application that uses a headless web browser, DrissionPage. The details or code are not relevant for the question.

I need to extract all fetch headers (such as cookies, user agent and others). And it needs to be using the browser console (Chromium).

In DrissionPage, I can get the result of an expression, as long as it is synchronous.

It is also possible (but not best practice) using asynchronous code, I could always do a while loop and wait until the promise is available, or insert it into the DOM from a callback and then read from DOM using drission selectors. But I would rather have a way to do it synchronously.

This is because run_js returns Any (and it does indeed return the expression result).

But run_async_js returns None.

I can get the results of a synchronous JS operation this way:

print(tab.run_js('''
        (() => {
            return 1;
        })()
        ''', as_expr=True))

So my question is, how can I extract the headers that would be sent by a fetch call? Preferably without firing the request or any async code for that matter.

how to open a url in user default browser in a trusted web activity app?

i have a twa appliction that is generated using pwabuilder website.
the website has a payment page that base on some policies the payment page should only open in user browser.
i use the below code to open the url in user default browser.

String query = Uri.encode(url, "UTF-8");
Intent browserIntent = new Intent(CATEGORY_BROWSABLE, Uri.parse(Uri.decode(query)));
browserIntent.setAction(ACTION_VIEW);
startActivity(browserIntent);

the problem is that that in xiaomi miui phones when code reach to startActivity(browserIntent) the payment page open in the app not in the user browser.
but when i test it in samsung apps it successfully got open in my phones browser.

i test the below code to and the problem with this code is that in miui phones when it reach to startActivity nothing happens:

Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(browserIntent);

however i find that if i give Display pop-up windows while running in the background permission to my app in miui phones the user browser opens successfully but i saw apps that can open users browsers in miui without this permission.

i search a lot but i don’t find any solution to this problem.

Vue component not rendering while I am trying to mount the component for unit testing

I am trying to write the unit test cases for my component but I am getting the error not able to render the component

Error I am getting

'''console.warn
  [Vue warn]: Component is missing template or render function. 
    at <Anonymous msg="Hello Jest" ref="VTU_COMPONENT" >
    at <VTUROOT>'''

And I am using vue.config.js as my config file

hello.vue

    <template>
    <div>
      <div>
        <h1>{{ msg }}</h1>
        <p>...</p>
      </div>
    </div>
  </template>
  
  <script>
  
  export default {
    props: {
      msg: {
        type: String,
        required: true
      }
    }
  }
  </script>


hello.spec.js

import HelloWorld from "../../src/hello.vue";
import { mount } from "@vue/test-utils";

describe("HelloWorld", () => {
    it("renders properly", () => {
        const wrapper = mount(HelloWorld, { props: { msg: "Hello Jest" } });

        expect(wrapper.text()).toContain("Hello Jest");
    });
})

;

Attaching some screen grab for better reference

enter image description here

Django – javascript – ajax does not work with javascript created lines

I have three classes lets say,

class Item(models.Model):
    id = models.AutoField(primary_key=True)  # Explicitly define the id column as the primary key
    itemref = models.IntegerField(blank=True, null=True)
    code = models.CharField(max_length=150, db_collation='Turkish_CI_AS', blank=True, null=True)
    tanimlama = models.CharField(max_length=150, db_collation='Turkish_CI_AS', blank=True, null=True)
    itemname = models.CharField(max_length=150, db_collation='Turkish_CI_AS', blank=True, null=True)

class SAFiche(models.Model):
     nothing important

class SALine(models.Model):
    safiche = models.ForeignKey(SAFiche, on_delete=models.CASCADE)
    item = models.ForeignKey('accounting.Item', on_delete=models.CASCADE)



In views, I have ajax for autocomplate:

def item_autocomplete(request):

    query = request.GET.get('q', '')
    items = Item.objects.filter(
        Q(code__icontains=query) |
        Q(itemname__icontains=query) |
        Q(tanimlama__icontains=query)
    )[:10]  # Limit to 10 results

    results = []
    for item in items:
        label = f"{item.code or ''} - {item.itemname or item.tanimlama or ''}"
        results.append({
            'id': item.id,
            'label': label.strip(' -'),
        })
    return JsonResponse(results, safe=False)

I properly adjusted rest. This is my forms:

class SALineCreateForm(forms.ModelForm):
    urun_search = forms.CharField(
        label="Ürün Ara",
        required=False,
        widget=forms.TextInput(attrs={
            'placeholder': 'Ürün kodu veya adı...',
            'class': 'item-search-input border rounded p-2 w-full',
            'autocomplete': 'off'
        })
    )

    class Meta:
        model = SALine
        fields = [
            'urun', 'miktar'
        ]
        widgets = {
            'urun': forms.HiddenInput(),
            'miktar': forms.NumberInput(attrs={'class': 'w-full form-input rounded-md'}),

        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # If editing an existing line, fill the search box with the item name
        if getattr(self.instance, 'urun_id', None):
            self.fields['urun_search'].initial = str(self.instance.urun)

SALineCreateFormSet = inlineformset_factory(
    SAFiche,
    SALine,
    form=SALineCreateForm,
    extra=1,        
    can_delete=True
)

Autocomplating works well with forms created inlines. However, when I use javascript with create new lines button, urun field does auto complate. Nothing happens on the console if I write something to newly created lines urun fields. What can be the problem? How can I solve it?

urun means item in turkish.

This is how I add new item lines:

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const formsetContainer = document.getElementById('formset-container');
        const emptyFormTemplate = document.getElementById('empty-form-template').innerHTML;
        const addProductButton = document.getElementById('add-product');

        addProductButton.addEventListener('click', () => {
            // Get current form count from the management form
            const totalFormsInput = document.querySelector('#id_saline_set-TOTAL_FORMS');
            const totalForms = parseInt(totalFormsInput.value, 10);

            // Create a new form from the empty template
            const newFormHTML = emptyFormTemplate.replace(/__prefix__/g, totalForms);
            formsetContainer.insertAdjacentHTML('beforeend', newFormHTML);

            // Update the TOTAL_FORMS count
            totalFormsInput.value = totalForms + 1;
        });
    });
</script>

And this is rest of the js code which handles autocomplate:

<script>
document.addEventListener('DOMContentLoaded', () => {
    const firmSearchInput = document.getElementById("id_firm_search");
    const firmHiddenInput = document.getElementById("id_firm");
    const resultsContainer = document.getElementById("firm-search-results");
    let selectedIndex = -1; // To track the currently highlighted index

    // Fetch data from the firm_autocomplete endpoint
    async function fetchFirms(query) {
        if (!query.trim()) {
            resultsContainer.innerHTML = "";
            resultsContainer.classList.add("hidden");
            return;
        }
        try {
            const response = await fetch("{% url 'operations:firm_autocomplete' %}?q=" + encodeURIComponent(query));
            const data = await response.json();
            renderResults(data);
        } catch (err) {
            console.error("Autocomplete error:", err);
        }
    }

    // Render the list of firm suggestions
    function renderResults(firms) {
        if (!firms.length) {
            resultsContainer.innerHTML = "";
            resultsContainer.classList.add("hidden");
            return;
        }

        selectedIndex = -1; // Reset selected index
        resultsContainer.innerHTML = firms
            .map((firm, index) => `
                <div class="px-3 py-2 hover:bg-gray-100 cursor-pointer"
                     data-index="${index}"
                     data-firm-id="${firm.id}"
                     data-firm-label="${firm.label}">
                    ${firm.label}
                </div>
            `).join('');
        resultsContainer.classList.remove("hidden");

        // Add click handlers for each suggestion
        const items = resultsContainer.querySelectorAll("[data-firm-id]");
        items.forEach((item, index) => {
            item.addEventListener("click", () => selectFirm(index));
        });
    }

    // Select a firm by index
    function selectFirm(index) {
        const items = resultsContainer.querySelectorAll("[data-firm-id]");
        if (index >= 0 && index < items.length) {
            const selectedFirm = items[index];
            const selectedFirmId = selectedFirm.getAttribute("data-firm-id");
            const selectedFirmLabel = selectedFirm.getAttribute("data-firm-label");

            // Update hidden input with the firm ID
            firmHiddenInput.value = selectedFirmId;
            // Update the visible text box
            firmSearchInput.value = selectedFirmLabel;

            // Hide the dropdown
            resultsContainer.innerHTML = "";
            resultsContainer.classList.add("hidden");
        }
    }

    // Trigger fetch on input
    firmSearchInput.addEventListener("input", (e) => {
        const query = e.target.value;
        fetchFirms(query);
    });

    // Keyboard navigation
    firmSearchInput.addEventListener("keydown", (e) => {
        const items = resultsContainer.querySelectorAll("[data-firm-id]");

        if (e.key === "ArrowDown") {
            // Move focus down
            e.preventDefault();
            selectedIndex = (selectedIndex + 1) % items.length;
            highlightItem(items, selectedIndex);
        } else if (e.key === "ArrowUp") {
            // Move focus up
            e.preventDefault();
            selectedIndex = (selectedIndex - 1 + items.length) % items.length;
            highlightItem(items, selectedIndex);
        } else if (e.key === "Enter") {
            // Select the currently highlighted item
            e.preventDefault();
            if (selectedIndex >= 0) {
                selectFirm(selectedIndex);
            }
        } else if (e.key === "Tab" && selectedIndex >= 0) {
            // Select and move to the next form element on Tab
            selectFirm(selectedIndex);
        }
    });

    // Highlight a specific item
    function highlightItem(items, index) {
        items.forEach((item, i) => {
            if (i === index) {
                item.classList.add("bg-gray-100");
            } else {
                item.classList.remove("bg-gray-100");
            }
        });
    }

    // Hide dropdown when clicking outside
    document.addEventListener("click", (e) => {
        if (!resultsContainer.contains(e.target) && e.target !== firmSearchInput) {
            resultsContainer.innerHTML = "";
            resultsContainer.classList.add("hidden");
        }
    });
});
</script>

<script>
document.addEventListener('DOMContentLoaded', () => {
    // 1. For each row in the formset, we have an input with class "item-search-input"
    const itemSearchInputs = document.querySelectorAll('.item-search-input');

    itemSearchInputs.forEach((input, index) => {
        const resultsContainerId = `item-search-results-${index}`;
        const resultsContainer = document.getElementById(resultsContainerId);

        const hiddenUrunInput = input
            .closest('.formset-row')
            .querySelector('input[name$="-urun"]');

        let selectedIndex = -1; // Tracks the currently highlighted index

        // Autocomplete fetch function
        async function fetchItems(query) {
            if (!query.trim()) {
                resultsContainer.innerHTML = '';
                resultsContainer.classList.add('hidden');
                return;
            }
            try {
                const url = "{% url 'operations:item_autocomplete' %}?q=" + encodeURIComponent(query);
                const response = await fetch(url);
                const data = await response.json();
                renderResults(data);
            } catch (err) {
                console.error("Item autocomplete error:", err);
            }
        }

        // Renders the dropdown results
        function renderResults(items) {
            if (!items.length) {
                resultsContainer.innerHTML = '';
                resultsContainer.classList.add('hidden');
                return;
            }

            selectedIndex = -1; // Reset selected index
            resultsContainer.innerHTML = items.map((item, i) => `
                <div class="px-3 py-2 hover:bg-gray-100 cursor-pointer"
                     data-index="${i}"
                     data-item-id="${item.id}"
                     data-item-label="${item.label}">
                    ${item.label}
                </div>
            `).join('');
            resultsContainer.classList.remove('hidden');

            // Click handlers
            const suggestionDivs = resultsContainer.querySelectorAll('[data-item-id]');
            suggestionDivs.forEach(div => {
                div.addEventListener('click', () => {
                    selectItem(div);
                });
            });
        }

        // Handle item selection
        function selectItem(element) {
            const selectedId = element.getAttribute('data-item-id');
            const selectedLabel = element.getAttribute('data-item-label');

            // Set hidden input to item id, visible input to label
            hiddenUrunInput.value = selectedId;
            input.value = selectedLabel;

            // Hide the dropdown
            resultsContainer.innerHTML = '';
            resultsContainer.classList.add('hidden');
        }

        // Highlight a specific item
        function highlightItem(items, index) {
            items.forEach((item, i) => {
                if (i === index) {
                    item.classList.add('bg-gray-100');
                } else {
                    item.classList.remove('bg-gray-100');
                }
            });
        }

        // Listen for typing
        input.addEventListener('input', (e) => {
            fetchItems(e.target.value);
        });

        // Listen for keyboard events
        input.addEventListener('keydown', (e) => {
            const items = resultsContainer.querySelectorAll('[data-item-id]');
            if (e.key === 'ArrowDown') {
                e.preventDefault();
                if (items.length > 0) {
                    selectedIndex = (selectedIndex + 1) % items.length;
                    highlightItem(items, selectedIndex);
                }
            } else if (e.key === 'ArrowUp') {
                e.preventDefault();
                if (items.length > 0) {
                    selectedIndex = (selectedIndex - 1 + items.length) % items.length;
                    highlightItem(items, selectedIndex);
                }
            } else if (e.key === 'Enter') {
                e.preventDefault();
                if (selectedIndex >= 0 && selectedIndex < items.length) {
                    selectItem(items[selectedIndex]);
                }
            } else if (e.key === 'Tab') {
                if (selectedIndex >= 0 && selectedIndex < items.length) {
                    selectItem(items[selectedIndex]);
                }
            }
        });

        // Optional: hide dropdown if user clicks outside
        document.addEventListener('click', (e) => {
            if (!resultsContainer.contains(e.target) && e.target !== input) {
                resultsContainer.innerHTML = '';
                resultsContainer.classList.add('hidden');
            }
        });
    });
});
</script>

Remote login Fetch API (POST) – Error 403 Forbidden

I have a problem logging in to a subdomain using the Fetch API, I get a 403 error

All traffic goes through cloudflare proxies:

subdomain.name.com – CNAME set to another server (I don’t have access)

name.com – This is my server connected to Cloudflare.

My script from main page https://name.com:

document.addEventListener('DOMContentLoaded', async () => {
          try {
            const loginPageResponse = await fetch('https://subdomain.name.com/auth/login/', {
              method: 'GET',
              credentials: 'include' // Wymagane do przesyłania ciasteczek
            });

            const loginPageHtml = await loginPageResponse.text();

            const parser = new DOMParser();
            const doc = parser.parseFromString(loginPageHtml, 'text/html');
            const csrfTokenElement = doc.querySelector('input[name="csrfmiddlewaretoken"]');

            const csrfToken = csrfTokenElement.value;

            const loginResponse = await fetch('https://subdomain.name.com/auth/login/', {
              method: 'POST',
              credentials: 'include',
              body: new URLSearchParams({
                next: '',
                csrfmiddlewaretoken: csrfToken,
                username: 'user',
                password: 'pass',
              })
            });

            if (loginResponse.ok) {
              console.log('Log in');
            } else {
              console.error('Error login:', loginResponse);
              const errorText = await loginResponse.text();
              console.error('Error:', errorText);
            }
          } catch (error) {
            console.error('err:', error.message);
          }
    });

Subdomain Headers

1. For request method GET (200 OK):

2. For request method OPTIONS (200 OK):

3. For request method Post (Error):

https://subdomain.name.com/auth/login/
Request Method:POST
Status Code:403 Forbidden
Remote Address: 0.0.0.0
Referrer Policy: strict-origin-when-cross-origin

Response Headers:

access-control-allow-credentials: true
access-control-allow-headers: Content-Type, Accept, X-Requested-With, Authorization, X-Custom-Header
access-control-allow-methods: GET, POST, PUT, DELETE, OPTIONS
access-control-allow-origin: https://name.com
alt-svc: h3=":443"; ma=86400
cache-control: no-cache
cf-cache-status: DYNAMIC
cf-ray: 8fe475734814c063-WAW
content-encoding: zstd
content-language: pl
content-type: text/html; charset=utf-8
date: Tue, 07 Jan 2025 13:57:46 GMT
nel: {"report_to":"default","max_age":2592000,"include_subdomains":true}
p3p: CP="ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI"
priority: u=1,i
report-to: {"group":"default","max_age":2592000,"endpoints":[{"url":"https://report.cdn.com/a/t/g"}],"include_subdomains":true}
server: cloudflare
set-cookie: csrftoken=Qtt3tcjt2mBhlUcFLrv65lckSIZ85pbu; Domain=subdomain.name.com; expires=Tue, 06 Jan 2026 13:57:46 GMT; Max-Age=31449600; Path=/; SameSite=none; Secure
strict-transport-security: max-age=31536000; includeSubDomains; preload
vary: Accept-Encoding
vary: Accept-Language, Cookie
via: 1.1 google
x-robots-tag: noindex, nofollow

Request Headers:

:authority: subdomain.name.com
:method: POST
:path: /auth/login/
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
accept-encoding: gzip, deflate, br, zstd
accept-language: en-EN,en;q=0.9
content-length: 118
content-type: text/html; charset=utf-8
cookie: test
origin: https://name.com
priority: u=1, i
referer: https://name.com/
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-site
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36

Results:

Error login: Response {type: 'cors', url: 'https://subdomain.name.com/auth/login/', redirected: false, status: 403, ok: false, …}

appendChild does not work in addEventListener

I tried to create an html file that adds a string each time a button is pressed with the following code:

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <form id="form">
            <button type="button" id="a">add</button>
        </form>
        <script>
            const txt = "hello world ";
            const el = document.createTextNode(txt);
            document.body.appendChild(el);
            const f1 = document.querySelector("#a");
            f1.addEventListener("click", () => {
                document.body.appendChild(el);
                console.log("hi");
            });
        </script>
    </body>
</html>

However, when I run this code, only document.body.appendChild(el) doesn’t work. Both console.log in the function and appendChild outside the function work well. Any ideas? Thanks

Java Spring thymeleaf, Can I use two context on the same form?

I am a Junior dev in internship and I would like have a correct answer about my problem…

I need to modify a web app for my company, my tutor want use a new context on a form for doing connected test (currently the form use a another context)
The problem is she want to had this method on the same form with a new button for connected test….

My major problem is the form only use the first orignal button and i can’t add a new button (if I do that the new button will act exactly like the old one)

With this new “connected” button I need to call my new context but right now I can’t see if my new context work because the current button doesn’t call this one.

So this is my redirect.html page

<div class="col col-md-6" id="PUCContext">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">PUC Context</h3>
</div>
<div class="panel-body">
<div th:if="${exception==null}">
<input hidden="hidden" id="submitPuc" th:value="${submitPuc}">

<form id="lanceurForm" target="lanceurFrame" hidden="hidden"
th:object="${hcontext}" th:action="${havanaPucUrl}"
method="post">

<div class="form-group">
<textarea th:field="*{ihmLaunchParams}" name="ihmLaunchParams"
rows='13' maxlenght='3000' class="form-control"
id="ihmLaunchParams"></textarea>
</div>


</form>

</div>

<div class="form-group">
<div class="input-group">
<div class="input-group-addon">Identifiant agence</div>
<select id="idAgence" name="idAgence" class="form-control">
<option th:each="elem : ${IdAgences.idAgences}"
th:value="${elem}" th:text="${elem}"></select>
</div>
</div>

{other data from my form ...}

<button id="submitPuc" type="submit" 
class="btn btn-default" >Test Bouchonné</button>
<button id="submitTestConnecte" type="submit"                                      class ="btn btn-default"  >Test Connecté </button>

</form>
</div>

and this is my redirect.js page :

  
    //Button "Test Bouchonné Context PUC"
    
    $(document).ready ( function () {
        
        $("#PUCEvents").hide();
        var submitPuc = $("#submitPuc").val();
        if(!!submitPuc){
            $( "#lanceurForm" ).submit();
            //$("#globalLanceur").hide(); 
            $("#BAMFormulaire").hide();
            $("#SAVCAFormulaire").hide(); 
            $("#PUCContext").hide();
            $("#PUCContext").hide();
            $("#PUCEvents").hide();
            $( "#PUCEvents" ).removeClass('col-md-3').addClass('col-md-  7').addClass('col-md-offset-3')
              $("#havana").show();
        } 
    
    //Button "Test connecté Context PUC" 
    
          $(document).ready(function () {
               $("#PUCEvents").hide();
               var submitTestConnecte = $("#submitTestConnecte").val();
               if (!!submitTestConnecte) {
                   $( "#lanceurForm2" ).submit();
                   // Hide forms and modify classes as you are doing
                   $("#BAMFormulaire").hide();
                   $("#SAVCAFormulaire").hide();
                   $("#PUCContext").hide();
                   $("#PUCEvents").hide();
                   $("#PUCEvents").removeClass('col-md-3').addClass('col-md-7').addClass('col-md-offset-3');
                   $("#havana").show();

                   // Redirect after a short delay to allow any submission process to complete
                   setTimeout(function() {
                       window.location.href = '/connecte';
                   }, 1000); 
               }    
           });
           

this page manages the redirection of my buttons

I’m trying to get my new button to work correctly so that it can call my context (so that I can test it)

Error while removing the report from Oracle APEX from Interactive Report

I have enabled the Oracle APEX Interactive Reports’ users to save the predefined search criteria as report. This is to ease the users to save them some time in future to narrow down the number of records being displayed when they log back in after a while.

When user is deleting the ‘pre-saved’ report, they get an error: “SAVED INTERACTIVE REPORT DOES NOT EXIST” follow by another error: “ERROR: Parsererror – SyntaxError: Unexpected non-whitespace character after JSON at position 2052 (line 33 column1)”

I have tried adding a dynamic action to force-refresh the site right after the deletion of the report but it does not seem to be working as expected: “apex.region(“region_static_id”).call(“refresh”);”

Any idea how to solve this? The thing is that the reports get deleted properly and after the user logs out and logs back in, the report no longer appears meaning it worked just fine. The error only pops up when I attempt to delete it from the interface end.

How to make simple client-side Recaptcha V2 validation?

The issue I’m experiencing with my script is that it is not properly validating whether the Google reCAPTCHA is completed before allowing users to submit the form. While the validations for inputs are working as expected and appropriately prevent submission when those fields are invalid, the validation logic for the reCAPTCHA appears to be Ignored entirely, allowing users to bypass the Captcha.

<form id="of_form_empresa">
    <div class="both">
        <input type="text" name="name" id="of_nome" class="of_inputtext_s" placeholder="Nome" onfocus="clearinput(this)">
        <input type="text" name="mail" id="of_mail" class="of_inputtext_s" placeholder="E-mail" onfocus="clearinput(this)">
    </div>
    <div class="both">
        <input type="text" name="phone" id="of_fone" class="of_inputtext" placeholder="Telefone" onfocus="clearinput(this)">
    </div>
    <div class="both">
        <textarea name="msg" id="of_msg" class="of_textarea" placeholder="Mensagem" onfocus="clearinput(this)"></textarea>
    </div>
    <div class="col-md-12 alcenter">
        <div id="recaptcha-empresa" class="g-recaptcha" data-sitekey="MY-SITE-KEY"></div>
    </div>
    <div class="both">
        <input type="button" value="Enviar" class="of_inputbutton" onclick="sendcontato()">
    </div>
</form>
<script>
function sendcontato(){
    form_ok = true;
    var form = document.getElementById("of_form_empresa");
    var name = form.elements['of_nome'].value;
    var mail = form.elements['of_mail'].value;
    var phone = form.elements['of_fone'].value;
    var msg = form.elements['of_msg'].value;
    
    var atpos=mail.indexOf("@");
    var dotpos=mail.lastIndexOf(".");
    
    if (name == ""){
        $("#of_nome").addClass("of_invalid");
        form_ok = false;
    }else{
        $("#of_nome").removeClass("of_invalid");
    }
    if (mail == "" || atpos<1 || dotpos<atpos+2 || dotpos+2>=mail.length){
        $("#of_mail").addClass("of_invalid");
        form_ok = false;
    }else{
        $("#of_mail").removeClass("of_invalid");
    }
    if (phone == ""){
        $("#of_fone").addClass("of_invalid");
        form_ok = false;
    }else{
        $("#of_fone").removeClass("of_invalid");
    }
    if (msg == ""){
        $("#of_msg").addClass("of_invalid");
        form_ok = false;
    }else{
        $("#of_msg").removeClass("of_invalid");
    }
    
    // Validate Captcha
    const captchaResponse = grecaptcha.getResponse();
    if (captchaResponse == "") {
        alert("Please complete the captcha.");
        form_ok = false;
    }
    
    if (form_ok){ 
        $.post("includes/envia.php",{name:name,mail:mail,phone:phone,msg:msg},function(data){
            // console.log(data);
            $("#of_okconsulta").fancybox().trigger('click');    
            document.getElementById("of_form_empresa").reset();
        }); 
    }
}
</script>

Fabricjs: incorrect polygon display after point update and expansion/rotation

I have a polygon in fabric.js. After updating its points everything works fine regarding moving the polygon, but when I try to stretch/rotate it then the polygon shows incorrectly.


    const canvas = new Canvas('canvas');

    const points = [
      {
        x: 3,
        y: 4,
      },
      {
        x: 16,
        y: 3,
      },
      {
        x: 30,
        y: 5,
      },
      {
        x: 25,
        y: 55,
      },
      {
        x: 19,
        y: 44,
      },
      {
        x: 15,
        y: 30,
      },
      {
        x: 15,
        y: 55,
      },
      {
        x: 9,
        y: 55,
      },
      {
        x: 6,
        y: 53,
      },
      {
        x: -2,
        y: 55,
      },
      {
        x: -4,
        y: 40,
      },
      {
        x: 0,
        y: 20,
      },
    ];

    const poly = new Polygon(points, {
      left: 200,
      top: 50,

      fill: 'yellow',
      strokeWidth: 1,
      stroke: 'grey',

      objectCaching: false,
      transparentCorners: false,
      cornerColor: 'blue',
    });

    canvas.add(poly);

    poly.on('modified', () => {
      const points = RecalculatePointsInPolygon(poly);
 
      poly.set({ points });
 
     poly.setCoords();
     poly.setBoundingBox(true);
    });

function RecalculatePointsInPolygon(polygon: Polygon): Point[] {
  const matrix = polygon.calcTransformMatrix();

  return polygon
    .get('points')
    .map(
      (p: Point) =>
        new Point(p.x - polygon.pathOffset.x, p.y - polygon.pathOffset.y),
    )
    .map((p: Point) => util.transformPoint(p, matrix));
}

Before i resize i have correct shape:
after move

but after resize (and rotate also):
after resize

It look like boundingBox is correctly implementent, but content inside is bigger. I also noted that points from RecalculatePointsInPolygon are counted corrected.

Please help i have no idea where i do mistake.

I expect correct shape after resize/rotate.

EDIT. I managed to make the polygon stretch correctly after calculating the points. Unfortunately I have no idea what to do with the rotation.

    poly.on('modified', () => {
      var width = poly.width;
      var height = poly.height;
      var scaleX = poly.scaleX;
      var scaleY = poly.scaleY;

      const points = RecalculatePointsInPolygon(poly);

      poly.set({
        points,

        width: width * scaleX,
        height: height * scaleY,
        scaleY: 1,
        scaleX: 1,
      });

      poly.setBoundingBox(true);
    });

With that code you can update points in polygon and modify this polygon without problem (except rotating).
So now after resize its look like should look:
how should look

But still there is a problem with rotation:
enter image description here

javascript question concerning multiple customised dropdowns in same page

I’m using a super JS script for a customised dropdown menu I found on CodePen.
Ive further customised it but now Im trying to insert the same dropdown multiple times on one page.
Here’s the link to the PEN where you can see the script.
Im sure I need to use ID’s but not sure how to modify the script appropriately.
Thanks.
LINK to Pen: https://codepen.io/pierre-laurent/pen/LVLgVw
Heres the JS code:

$(document).ready(function() {

  var countOption = $('.old-select option').length;

  function openSelect() {
    var heightSelect = $('.new-select').height();
    var j = 1;
    $('.new-select .new-option').each(function() {
      $(this).addClass('reveal');
      $(this).css({
        'box-shadow': '0 1px 1px rgba(0,0,0,0.1)',
        'left': '0',
        'right': '0',
        'top': j * (heightSelect + 1) + 'px'
      });
      j++;
    });
  }

  function closeSelect() {
    var i = 0;
    $('.new-select .new-option').each(function() {
      $(this).removeClass('reveal');
      if (i < countOption - 3) {
        $(this).css('top', 0);
        $(this).css('box-shadow', 'none');
      } else if (i === countOption - 3) {
        $(this).css('top', '3px');
      } else if (i === countOption - 2) {
        $(this).css({
          'top': '7px',
          'left': '2px',
          'right': '2px'
        });
      } else if (i === countOption - 1) {
        $(this).css({
          'top': '11px',
          'left': '4px',
          'right': '4px'
        });
      }
      i++;
    });
  }

  // Initialisation
  if ($('.old-select option[selected]').length === 1) {
    $('.selection p span').html($('.old-select option[selected]').html());
  } else {
    $('.selection p span').html($('.old-select option:first-child').html());
  }

  $('.old-select option').each(function() {
    newValue = $(this).val();
    newHTML = $(this).html();
    $('.new-select').append('<div class="new-option" data-value="' + newValue + '"><p>' + newHTML + '</p></div>');
  });

  var reverseIndex = countOption;
  $('.new-select .new-option').each(function() {
    $(this).css('z-index', reverseIndex);
    reverseIndex = reverseIndex - 1;
  });

  closeSelect();


  // Ouverture / Fermeture
  $('.selection').click(function() {
    $(this).toggleClass('open');
    if ($(this).hasClass('open') === true) {
      openSelect();
    } else {
      closeSelect();
    }
  });


  // Selection 
  $('.new-option').click(function() {
    var newValue = $(this).data('value');

    // Selection New Select
    $('.selection p span').html($(this).find('p').html());
    $('.selection').click();

    // Selection Old Select
    $('.old-select option[selected]').removeAttr('selected');
    $('.old-select option[value="' + newValue + '"]').attr('selected', '');

    // Visuellement l'option dans le old-select ne change pas
    // mais la value selectionnée est bien pris en compte lors 
    // de l'envoi du formulaire. Test à l'appui.

  });
});
/* Design tiré du site flatuicolors.com */

/* Réinitialisation */

*{
    margin: 0; padding: 0;
    box-sizing:         border-box;
    -moz-box-sizing:    border-box;
    -webkit-box-sizing: border-box;
}

body{font-family: 'Open Sans';}
a{color: #000; text-decoration: none;}
img{max-width: 100%;}
img a{border: none;}
ul{list-style-type: none;}

/* Fin Réinitialisation */

/* Démo */

body{
    background: #f5f5f5;
    padding-bottom: 30px;
}

.demo{
    width: 600px;
    margin: 100px auto 0 auto;
}

.demo h2{
    font-weight: 300;
    color: #444;
    font-size: 1.4em;
    border-bottom: 1px solid #444;
    padding-bottom: 5px;
    margin-bottom: 20px;
    letter-spacing: 1px;
}

.demo p{
    font-weight: 300;
    color: #444;
    font-size: 0.9em;
    line-height: 25px;
    margin-bottom: 40px;
    text-align: justify;
}

.demo span{
    display: block;
    font-weight: 300;
    font-style: italic;
    font-size: 0.75em;
    text-align: center;
    color: #6a6a6a
}

.demo span a{
   color: #6a6a6a 
}

.demo span a:hover{
   text-decoration: underline;
}

/* Old-Select */

.old-select{
    position: absolute;
    top: -9999px;
    left: -9999px;
}

/* New-Select */

.new-select{
    width: 300px;
    height: 50px;
    margin: auto;
    
    margin-top: 50px;
    text-align: center;
    color: #444;
    line-height: 50px;
    position: relative;
}

.new-select .selection:active{
    transform:         rotateX(42deg);
    -o-transform:      rotateX(42deg);
    -ms-transform:     rotateX(42deg);
    -moz-transform:    rotateX(42deg);
    -webkit-transform: rotateX(42deg);
    transform-style:         preserve-3d;
    -o-transform-style:      preserve-3d;
    -ms-transform-style:     preserve-3d;
    -moz-transform-style:    preserve-3d;
    -webkit-transform-style: preserve-3d;
    transform-origin:         top;
    -o-transform-origin:      top;
    -ms-transform-origin:     top;
    -moz-transform-origin:    top;
    -webkit-transform-origin: top;
    transition:         transform         200ms ease-in-out;
    -o-transition:      -o-transform      200ms ease-in-out;
    -ms-transition:     -ms-transform     200ms ease-in-out;
    -moz-transition:    -moz-transform    200ms ease-in-out;
    -webkit-transition: -webkit-transform 200ms ease-in-out;
}

.new-select .selection{
    width: 100%;
    height: 100%;
    background-color: #fff;
    box-shadow: 0 1px 1px rgba(0,0,0,0.1);
    
    cursor: pointer;
    position: relative;
    z-index: 20; /* Doit être supérieur au nombre d'option */
    
    transform:         rotateX(0deg);
    -o-transform:      rotateX(0deg);
    -ms-transform:     rotateX(0deg);
    -moz-transform:    rotateX(0deg);
    -webkit-transform: rotateX(0deg);
    transform-style:         preserve-3d;
    -o-transform-style:      preserve-3d;
    -ms-transform-style:     preserve-3d;
    -moz-transform-style:    preserve-3d;
    -webkit-transform-style: preserve-3d;
    transform-origin:         top;
    -o-transform-origin:      top;
    -ms-transform-origin:     top;
    -moz-transform-origin:    top;
    -webkit-transform-origin: top;
    transition:         transform         200ms ease-in-out;
    -o-transition:      -o-transform      200ms ease-in-out;
    -ms-transition:     -ms-transform     200ms ease-in-out;
    -moz-transition:    -moz-transform    200ms ease-in-out;
    -webkit-transition: -webkit-transform 200ms ease-in-out;
}

.new-select .selection p{
    width: calc(100% - 60px);
    position: relative;
    
    transition:         all 200ms ease-in-out;
    -o-transition:      all 200ms ease-in-out;
    -ms-transition:     all 200ms ease-in-out;
    -moz-transition:    all 200ms ease-in-out;
    -webkit-transition: all 200ms ease-in-out;
}

.new-select .selection:hover p, .new-select .selection.open p{
    color: #bdc3c7;
}

.new-select .selection i{
    display: block;
    width: 1px;
    height: 70%;
    position: absolute;
    right: -1px; top: 15%; bottom: 15%;
    border: none;
    background-color: #bbb;
}

.new-select .selection > span{
    display: block;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 14px 8px 0 8px; /* Height: 14px / Width: 16px */
    border-color: #bbb transparent transparent transparent;
    
    position: absolute;
    top: 18px; /* 50 / 2 - 14 / 2 */
    right: 22px; /* 60 / 2 - 16 / 2 */
}

.new-select .selection.open > span{
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 0 8px 14px 8px;
    border-color: transparent transparent #bbb transparent;
}

.new-option{
    text-align: center;
    background-color: #fff;
    cursor: pointer;
    box-shadow: 0 1px 1px rgba(0,0,0,0.1);
    position: relative;
    margin-top: 1px;
    
    position: absolute;
    left: 0; right: 0;
    
    transition:         all 300ms ease-in-out;
    -o-transition:      all 300ms ease-in-out;
    -ms-transition:     all 300ms ease-in-out;
    -moz-transition:    all 300ms ease-in-out;
    -webkit-transition: all 300ms ease-in-out;
}

.new-option p{
    width: calc(100% - 60px);
}

.new-option.reveal:hover{
    background-color: #444;
    color: #f5f5f5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

<!-- Design tiré du site flatuicolors.com -->
<!-- Bouton Select de base -->
<select class="old-select">
    <option value="html">HTML</option>
    <option value="css">CSS</option>
    <option value="sass">SASS</option>
    <option value="javascript">Javascript</option>
    <option value="jquery" selected>jQuery</option>
</select>

<!-- Bouton Select reconstruit -->
<div class="new-select">
    <div class="selection">
        <p>
            <span></span>
            <i></i>
        </p>
        <span></span>
    </div>
</div>

<div class="demo">
    
    <h2>Initialisation</h2>
    <p>Chaque option du menu select (.old-select) est copiée afin de créer une nouvelle option dans le nouveau menu select (.new-select) qui portera dans un "data" sa "value". Le choix d'une option est ensuite fait afin de remplir le bloc ".selection". Si une des options porte l'attribut "selected", elle est choisis. Sinon, la première est sélectionnée. Une fois toutes les options "reconstruite", on inverse les z-index afin que la première soit devant, la deuxieèe ensuite, etc.</p>
    
    <h2>Ouverture / Fermeture</h2>
    <p>Les "new-option" étant positionnées en "absolute", on alterne simplement entre deux états avec des règles CSS différentes. Quelques règles diffèrent pour les trois dernières afin de respecter le design.</p>
    
    <h2>Sélection</h2>
    <p>Quand une "new-option" est selectionnée, ses données sont récupérées afin de :<br>- Indiquer la selection dans le "new-select"<br>- Intégrer l'attribut "selected" à l'option qui porte la même "value", après suppression pour celle qui le portait déjà.<br>Bien que visuellement, aucun changement ne se produit dans le "old-select", l'attribut est bien pris en compte lors de l'envoi du formulaire.</p>
    
    <span>Design tiré du site <a href="http://flatuicolors.com">flatuicolors.com</a></span>
</div>        

`jest.spyOn()` Works on Imported Module but Failed on Imported Function of the Same Module

What Works vs. What Doesn’t

The Unit Test

// my-func.test.js
import { jest } from '@jest/globals';
import { myFunc } from './my-func.js';
import fs from 'node:fs';

const mock = jest.fn().mockReturnValue({isDirectory: () => true});
jest.spyOn(fs, 'statSync').mockImplementation(mock);

it('should work', () => {
  expect(myFunc('/test')).toBeTruthy();

  expect(mock).toHaveBeenCalled();
});

The Working Version

// my-func.js
import fs from 'node:fs';
// import { statSync } from 'node:fs';

export function myFunc(dir) {
  const stats = fs.statSync(dir, { throwIfNoEntry: false });
  // const stats = statSync(dir, { throwIfNoEntry: false });
  return stats.isDirectory();
}

The Failing Version

Note the different importing mechanism. Jest will fail with:

Cannot read properties of undefined (reading ‘isDirectory’)
TypeError: Cannot read properties of undefined (reading ‘isDirectory’)

// my-func.js
// import fs from 'node:fs';
import { statSync } from 'node:fs';

export function myFunc(dir) {
  // const stats = fs.statSync(dir, { throwIfNoEntry: false });
  const stats = statSync(dir, { throwIfNoEntry: false });
  return stats.isDirectory();
}

The Question

How do I spy on or mock statSync using the import { statSync } from 'node:fs'; syntax, or is it even possible?

Environment

Note that the test is being run in the ESModule mode.

# package.json
{
  "type": "module",
  "scripts": {
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
    ...
  },
  "devDependencies": {
    "@eslint/js": "^9.17.0",
    "@jest/globals": "^29.7.0",
    "globals": "^15.14.0",
    "jest": "^29.7.0",
    ...
  },
  ...
}

How to make a forced pattern using Javascript

I am trying to force the a pattern to be followed, forcing the first letter of a word capital and the rest lowercase but when I type a special character it turns the following character uppercase and I can’t figure out why.

document.addEventListener("DOMContentLoaded", function () {
            const displaynameInput = document.getElementById('display_name');

            function sanitizeDisplayNameInput() {
                let value = displaynameInput.value;

                const minLength = 2; // Minimum length for display name
                const maxLength = 50; // Maximum length for display name

                if (!/^[a-zA-Z]/.test(value)) {
                    value = value.replace(/^[^a-zA-Z]+/, ''); // Remove all non-alphabet characters at the start
                }

                if (value.replace(/s/g, '').length > maxLength) {
                    value = value.substring(0, value.length - 1);
                    displaynameInput.value = value;
                    return;
                }

                value = value.replace(/^s+/g, '');    // Remove leading spaces only

                value = value.replace(/s{2,}/g, ' '); // Replace multiple spaces with a single space

                // Ensure the value is between min and max length
                if (value.replace(/s/g, '').length < minLength || value.replace(/s/g, '').length > maxLength) {
                    document.getElementById("display_name-check-btn").style.display = "none";
                } else {
                    document.getElementById("display_name-check-btn").style.display = "block";
                }

                // Remove any words with non-English characters
                value = value.replace(/[^x00-x7F]/g, '');

                // Split the value into words
                let parts = value.split(' ');

                // Process each part
                value = parts
                    .map((word, index) => {
                        if (word.length === 0) return ''; // Skip empty words

                        // Convert the whole word to lowercase and then capitalize the first letter
                        return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();

                        // Allow any other characters or numbers at the start of words
                        return word;
                    })
                    .join(' '); // Rejoin the parts into a single string

                // Update the display name input with the sanitized value
                displaynameInput.value = value;
            }

            // Add event listeners
            displaynameInput.addEventListener('input', sanitizeDisplayNameInput);
            displaynameInput.addEventListener('blur', function () {
                sanitizeDisplayNameInput();
                // Remove trailing spaces when the user leaves the field
                displaynameInput.value = displaynameInput.value.trimEnd();
            });
        });
.login form input[type="password"], .login form input[type="text"], .login form input[type="email"], .register form input[type="password"], .register form input[type="text"], .register form input[type="email"] {
    width: 80%;
    height: 50px;
    border: 1px solid #dee0e4;
    border-left: 0;
    margin-bottom: 20px;
    padding: 0 15px;
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
    outline: 0;
<input type="text" name="display_name" placeholder="Display Name" id="display_name" required minlength="2" maxlength="50" style="text-transform: capitalize;" tabindex="3" oninvalid="alert('Please enter a valid display name!');" pattern="^[A-Z][a-z0-9W_]*( (?! )([A-Z]|[0-9W_])[a-z0-9W_]*)*$">
<input type="submit" value="Register" tabindex="6" id="submit" name="submit" style="margin-top:10px; width:400px;">

Can anyone please help? It doesn’t seem to work here in this form but you can check my website. I don’t know if I am allowed to post it here though but it is on my profile picture.