Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ repos:
rev: v2.4.1
hooks:
- id: codespell
args: ["--ignore-words-list", "notin"]
args: ['--ignore-words-list', 'notin']
exclude: 'yarn.lock|poetry.lock'
additional_dependencies:
- tomli
Expand Down
168 changes: 84 additions & 84 deletions beam/beam/custom/user.json
Original file line number Diff line number Diff line change
@@ -1,85 +1,85 @@
{
"custom_fields": [
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": null,
"dt": "User",
"fetch_if_empty": 0,
"fieldname": "barcode_section",
"fieldtype": "Section Break",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 26,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "mobile_no",
"label": "Barcodes",
"length": 0,
"module": "BEAM",
"name": "User-barcode_section",
"no_copy": 0,
"non_negative": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": null,
"dt": "User",
"fetch_if_empty": 0,
"fieldname": "barcodes",
"fieldtype": "Table",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 27,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "barcode_section",
"length": 0,
"module": "BEAM",
"name": "User-barcodes",
"no_copy": 0,
"non_negative": 0,
"options": "Item Barcode",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0
}
],
"doctype": "User",
"property_setters": [],
"sync_on_migrate": 1
}
"custom_fields": [
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": null,
"dt": "User",
"fetch_if_empty": 0,
"fieldname": "barcode_section",
"fieldtype": "Section Break",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 26,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "mobile_no",
"label": "Barcodes",
"length": 0,
"module": "BEAM",
"name": "User-barcode_section",
"no_copy": 0,
"non_negative": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": null,
"dt": "User",
"fetch_if_empty": 0,
"fieldname": "barcodes",
"fieldtype": "Table",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 27,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "barcode_section",
"length": 0,
"module": "BEAM",
"name": "User-barcodes",
"no_copy": 0,
"non_negative": 0,
"options": "Item Barcode",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0
}
],
"doctype": "User",
"property_setters": [],
"sync_on_migrate": 1
}
3 changes: 2 additions & 1 deletion beam/beam/demand/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pathlib
import sqlite3
from typing import Any

import frappe
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
Expand Down Expand Up @@ -153,7 +154,7 @@ def reset_receiving_db() -> None:
cursor.execute("DELETE FROM receiving")


def dict_factory(cursor: sqlite3.Cursor, row: sqlite3.Row) -> frappe._dict:
def dict_factory(cursor: sqlite3.Cursor, row: tuple[Any, ...]) -> frappe._dict:
_dict = frappe._dict()
for idx, col in enumerate(cursor.description):
_dict[col[0]] = row[idx]
Expand Down
164 changes: 164 additions & 0 deletions beam/tests/mobile/test_camera_scanner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Copyright (c) 2024, AgriTheory and contributors
# For license information, please see license.txt

# Tests for camera scanner component.


from playwright.sync_api import expect


def test_camera_scanner_button_hidden(page, setup):
page.add_init_script(
"""
Object.defineProperty(navigator, 'mediaDevices', {
value: {
enumerateDevices: async () => {
return []; // No cameras available
},
getUserMedia: async (constraints) => {
const canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = 480;
return canvas.captureStream(30);
}
},
});
"""
)

page.get_by_text("Move").click()
page.wait_for_url("**/beam#/move")

# Wait for component to check permissions and decide to hide itself
page.wait_for_timeout(1500)

# The component should not be visible when no cameras are found
camera_scanner = page.locator(".camera-scanner")
expect(camera_scanner).to_have_count(0)


def test_camera_scanner_button_visible(page, setup):
page.add_init_script(
"""
navigator.mediaDevices.enumerateDevices = async () => {
return [
{
kind: 'videoinput',
deviceId: 'mock-camera-1',
label: 'Mock Camera',
groupId: 'mock-group'
}
];
};
"""
)

page.get_by_text("Move").click()
page.wait_for_url("**/beam#/move")

page.wait_for_timeout(1000)

camera_button = page.locator("button:has-text('Open Camera')")
expect(camera_button).to_be_visible()


def test_camera_scanner_activates_camera(page, setup):
page.add_init_script(
"""
window.getUserMediaCalled = false;
window.getUserMediaConstraints = null;

Object.defineProperty(navigator, 'mediaDevices', {
value: {
enumerateDevices: async () => {
return [
{
kind: 'videoinput',
deviceId: 'mock-camera-1',
label: 'Mock Camera',
groupId: 'mock-group'
}
];
},
getUserMedia: async (constraints) => {
window.getUserMediaCalled = true;
window.getUserMediaConstraints = constraints;
// Return a minimal fake stream
const canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = 480;
return canvas.captureStream(30);
}
},
writable: false,
configurable: true
});
"""
)

page.get_by_text("Move").click()
page.wait_for_url("**/beam#/move")
page.wait_for_timeout(2000)

was_called_before = page.evaluate("window.getUserMediaCalled")
assert was_called_before == False, "getUserMedia should not be called before clicking button"

camera_button = page.locator("button:has-text('Open Camera')")
expect(camera_button).to_be_enabled(timeout=10000)

camera_button.click()
page.wait_for_timeout(1000)

was_called_after = page.evaluate("window.getUserMediaCalled")
assert was_called_after == True, "getUserMedia should be called after clicking 'Open Camera'"

# Verify correct constraints (should request video with back camera)
constraints = page.evaluate("window.getUserMediaConstraints")
assert constraints is not None, "getUserMedia should receive constraints"
assert "video" in constraints, "Should request video stream"
assert constraints["video"]["facingMode"] == "environment", "Should request back camera"


def test_camera_scanner_permission_denied(page, setup):
# Mock camera APIs to simulate permission denial
page.add_init_script(
"""
Object.defineProperty(navigator, 'mediaDevices', {
value: {
enumerateDevices: async () => {
return [
{
kind: 'videoinput',
deviceId: 'mock-camera-1',
label: 'Mock Camera',
groupId: 'mock-group'
}
];
},
getUserMedia: async (constraints) => {
const error = new Error('Permission denied');
error.name = 'NotAllowedError';
throw error;
}
},
writable: false,
configurable: true
});
"""
)

page.get_by_text("Move").click()
page.wait_for_url("**/beam#/move")
page.wait_for_timeout(2000)

camera_button = page.locator("button:has-text('Open Camera')")
expect(camera_button).to_be_enabled(timeout=10000)

camera_button.click()

page.wait_for_timeout(500)

# Verify error message is displayed
error_message = page.locator(".error-message")
expect(error_message).to_be_visible()
expect(error_message).to_contain_text("Permission denied")
Loading
Loading